Design Patterns: Don't Repeat Yourself in C#

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
have you ever heard the term dry as in keep your coat dry dry refers to one of the foundational principles in programming it stands for don't repeat yourself on the surface that sounds so easy to do however as you'll see in this video there's a lot to think through when trying to get this right before you get started I want to mention that today's video is sponsored by PVS studio PVS studio develops a static code analyzer that he text bugs and potential vulnerabilities in your code not only does it work in c-sharp but it also works in c and c++ and java support is coming soon check out the length of the description for more details and while you're in the description also note there's a link to a blog post for this video that blog post also includes links to the source code for this video ok let's look at our starter code here we have a simple wind form application we have just the dashboard form and I don't we have a couple of fields first name and last name we're going to generate an employee ID obviously this is a demo application not something real-world in that you probably wouldn't do it this way but it represents real-world stuff ok so let's run this real quick just to kind of show off what it does do so if I were it puts Timothy Cory and hit generate employee ID it takes my first 4 letters my first names the first four letters of my last name and then puts a three digit code on the end this is probably the logic you've seen in a number of different applications you've looked at if you have a code behind for this you'll see that there is a click event inside there is our code pretty simple stuff but there's a problem here and that is it does not conform to dry you may be thinking well yeah it does because it only does things once and we're not repeating ourselves and that's kind of true there's a little bit of repetition address but that's not the only repetition to look out for okay so we're gonna address this one step at a time and kind of work our way into a really dry scenario a scenario where we're set for the future as well as what we have today so let's first start by looking at the code we have so a string employee ID gets created by taking our first name and taking the the text field of that that box so it's right here it grabs that text box and says okay give me a substring of that and it's starts at position zero and gave me four characters it's let's I get my first four characters my first name and then it says okay last name text and then a substring and then four characters and then we do at the end date time now millisecond so we're at the bat you'll see a little bit of repetition in this call and this call now you could say well I can break that off into a method and yeah you can but here's the issue you already have it's called substring okay so this is a method and we've called it twice instead of actually doing the work twice so I'm not really sure that's a place where we can eliminate much code or reduce our repetition because it really is just calling a method and therefore not really repetition however imagine for a minute that this is not the only way we're going to create employees because it probably isn't imagine if we get a list of new employees to create and so we have its list of first names and last names we're going to generate employee IDs probably we wouldn't want to enter them one by one we want to have some way of uploading all those at once which means that this logic right here behind the generate employee ID that logic is going to need to be called somewhere else besides and the click event so there's a problem now we will have to repeat ourselves so part of dry is not just saying okay have repetition eliminate make a method out of it and then call it two places part of it is looking ahead and saying I am going to have repetition therefore I will solve this so the first way you can eliminate repetition order or allow yourself to not repeat yourself in the future is to create a method so here I would do something like this where I say let's make a private void and we'll say generate employee ID I'm gonna take all this code we can cut it out here we can paste it over here and then it's call generate employee ID now that didn't do a whole lot except for add one new method on the surface but when it comes time to make another button over here it says you know process CSV file that has multiple in it they're not gonna do that for time sake but let's pretend we're doing that well then we can call this same generate employee ID but we do have a problem here and that is that we're expecting that we know where the first name and last name come from so I think the next step is to say well actually let's do string first name and string last name and then pass those in like so it's a first name text let's do the same for last name text now we can call this method from somewhere else it does not have to be on here now the last mantle we have to look at though is where a scientist employee ID to the employee ID text text that's also a problem because the fact that we might not be writing it to this spot right here every time well in that case we can return a string and then we can do this we can cut this out and say return and then up here say employee tax text equals and call a method so now we this is a little bit of refactoring right now but we've done have you pulled out the the code behind for this click event and made it so we can call this method and does not depend on getting leave the name from these two boxes and does not depend on putting the name into this box this allows us to not repeat ourselves when we're not using the same exact form okay so that's step one is may it just make a method out of it that way when you create that second button that the process of the CSV file you have the code here now imagine our boss comes to us and says you know what we need to rework our user interface we need to have you know more power in the design maybe when I have animations or when I use the graphics card for some reason showing videos which kind of things or we just want to have a nice resizable user interface we just can't get that really well with with WinForms so we want to change over to death and so go ahead and build a new user interface so you come over here and create this new user interface and let's call it WPF UI now let's pretend this is a new project or you're starting over and you need to basically recreate this but in WPF so I pre wrote some code just to you know kind of save some time here and laid the form back out because that's not really what I'm discussing here let's go ahead and change the font size here let's make it 20 sounds good alright so we have this form now where we can fill in the first name a last name and click the generate employee ID and create the employee ID so let's go ahead and you know create this event over here let's go to let's just do a click event click equals new event and then we'll go the code behind it for that and what we do well if we're in the repeat ourselves we would confidence right here and paste it over here I'm that's not what we should be doing though we don't want to repeat ourselves and so the next step in this process of dry is anticipating the idea that you might want to change your user interface at some point because the user interfaces change and business requirements change and that's where the next solution comes in and that is the class library so create a class library will create in.net framework for now it will call this the dry demo library and we'll get rid of class 1 let's now let's create a new class we can call this what's called some like employee processor not a great name it not horrible yet public and what creates will actually paste that method in make it public and so now we have this public class employee processor right now it we don't need to instantiate it because it's not much to do here it could make a static class we wanted to it's kind of fatal who want let's let's leave it alone they've got some future plans for this so they're even beyond we have here so let's leave it alone as a non-static class mean you have to instantiate it so we can do is now we can come back over here to our dashboard you can cut this out because we don't want to have it in two places one of the purposes of dry is that we're not repeating ourselves so we're not creating bugs for ourself later and here's the reason why I'm going to show you a bug and then we're not gonna fix it now I want to show you the bug first just so you can kind of see the reason why we would have we do not line to from places so if I say Tim Cory and hit generate employee ID it crashes and the reason why is because Tim is three letters and I'm saying give me four letters out of that string you can't do it and so it crashes well if I was a good programmer I said okay I'm gonna fix this let's just pretend this is the fix it's not the fix but to change it to three okay and I run this and I were say Tim Cory and generate employee idea it does so if you know quote-unquote fix the problem I have not but pretend I did now I go whoo I'm done well there's a problem here I didn't change it over here into this code will still crash under those same circumstances where the other code won't and so now I have code in two different places that are in two different states me I fixed the bug in one place but not the other you may think well that's do you know no problem I can change it in both places I'll remember trust me you won't and maybe you will in a tiny project but as the project grows you won't and as you have slightly different iterations of the code in different spots it makes it much more difficult to handle so if you have this in 5 10 15 places you're just up a creek and so what happens is you fix a bug and the bug happens again you're like may I thought for sure I fixed it in fact you had in one spot but not in the other and that's why you really really really don't want to have code that's very very similar in two different places so if you can get rid of it and now this is gotta yell at us but not a problem because we can say actually it's employee processor and I'll do a controlled dot here to add a reference to my dry library and I'll say it's called processor equals new processor and now I can just say processor dot generate employee ID and now I'm calling it over here in my dashboard of my winform project so this this form right here is now calling up correctly from the class library now I can come over here to my my zamel and the code behind for that is here and I can say the same code employee processor the control dot and add the reference notice the control dot allows me to add a reference and also add the using statement all-in-one and yes this looks very similar in that fact it is it's instantiating the the answers of an employee processor but that's not repetition of ourselves not really okay so we can say let's get the name of our our field here our field is called employee ID so we can say employee ID dot text equals processor dot generate employee ID first name dot text and last name dot text and those are the two fields on our WPF form so now if we set this a start up project let me run it iris say the bug is still there so what to say Timothy Cory now I click the generate employee ID and it's the correct employee ID that does last three are a random number essentially it's the milliseconds of when I ran its code so that right there generates for us a employee ID on the wind are on the DPF side as well as the wind form and yet we're only using our code once okay so there we've now gone step further in eliminating repetition we're not done yet now let me before I go just see you're where I will be very clear on this I said it's kind of like a random number that's right here meaning it looks that way don't use the milliseconds as a random number okay there's actually a random number class that does random numbers I'm just using this to have something different on the end every time that's all I'm doing it for this is not a random number okay all right so now we've gone a step further and we have this dry demo library and we can use from either a win form product or a WPF project and you may say Tim that's great but here's the problem I don't necessarily want to use a second UI in my same solution understandable but maybe there's the same code used in other places in the organization so maybe you do the UI stuff in this solution but there's a different project that is just a Windows service and what it does is it takes the the forms they've been fill out on the website or something like that and it processes them and it creates our employee IDs that way well how do you handle that well part of the the benefit here the class library we right-click on this an open folder and file explorer and let's bring this down here we go to the bin folder and right now you've only compiled it in the debug branch and that's fine so it's got a debug we have this DLL here that DLL represents the code for this library so let's do this let's create entirely new solution so let's close our solution and let's do a new project and let's just for simplicity sake let's create a console application let's pretend it's to this service typically services our console applications are something simple to that so let's call this our console UI and let's call this our our dry demo to now in here why does something similar let's start with console dot readline and here we want to call that same code you've already written we don't want to repeat ourselves well we can do is add a reference now built in as a whole bunch of references we can access in the.net framework there's also two other projects but we don't have another project in the solution we go a shared project so you wanted to but let's go a browse instead and we'll hit browse now I'm going to do it's going to grab the path to our DLL so I copy that I'm gonna paste it in here and there's our DL out the only has pointed out I'll spray on-screen the ones in the bin debug folder I've grabbed that specific DLL and I said add I hit OK and so you can see right here the dry demo library is now in our list now if you forget which what things are in there but you induce a dry demo library dot it's employee processor dot of course there's nothing you do there so you can say don't drive down a library dot employee processor call it processor equals new processor and you can say well that's really wordy no problem now that you've you use intellisense to create you can delete the dry dental processor and just control dot a day using statement or you can just write yourself but now I can say processor dot generate employee ID and we need our first name our lasting and I don't have one of these yet so it's gonna yell at me now a problem let's just say console dot right what is your first name and then we'll say string first name equals console dot read line it'll do the exact same thing for last name there's last name and we'll read that too and so now we've read the first name and last name we've generated the employee ID so let's just say string employee ID equals and now I can say is console.writeline let's do our dollar sign here the or let's say your employee ID is employee ID all right so now if we run this application reignover Timothy Cory my employee ID is ti amo cor e96 one so the same code works now in a different application so not only have I kept my code dry in my first solution where I've been able to call it from one live or one UI and then the next UI i've also allowed myself to call it from a different UI entirely that's not in my solution by bringing in that dll now every time at dll changes you don't want to bring the new version of it the good news is and it's not covered in this video but where i do it soon he is you can actually create a new get package for your DLL and then you have a private nougat server if you want or a public one if you want to use a public one but you can bring that new get package in and then use the normal nougat package update routine that you need only with any other package but it's for your stuff but we're not done yet so let's let's run this again and let's put in Tim and Cory again and notice how it crashes it actually crashes in our deal out which is really cool because our DLL it knows how to go right into that code even though it's not in our solution so says hey there's a problem here so we don't leave that problem with the first name so maybe we do is let's go ahead and open our our solution so I'll grab the dry demo instead try that on - and let's go ahead and fix this code so when you write some code for it but here's a problem if I fix this code for first name and I run it again it all works and I push push this to production I'm gonna have to fix it again for last name at some point now the tests again and here is one of the areas that people often overlook dry and that is your development work so before I start fixing this I think the best solution in one of the ways we can better implement dry or not repeating ourselves is to add a new solution to our new project to this solution make a class library and let's call this the dry demo library tests or tests project now let's go ahead and delete class one and then we'll add our references our new you package references to X unit there we go I can install that once that's done we can go ahead and close this out and let's add a new test class so add class let's call this class the employee processor test class all right at this point we can do make it public then I can add in our using statements for X units and then here let's create our first and probably only test method we can view that out so let's create a method that's going to look at the generate employee ID so call this the once a public void generate employee ID should calculate and we'll make this a theory actually so Gramp pass in first name last name and this is gonna be the expected start I call it and this is a little tricky because of the fact that the the whole string has that number at the end that's really hard to work with and so it's hard to create tests for it but we're gonna do that create this test by saying well the first part of it should start with this and that's what they do sometimes now really what should happen is this is kind of getting the unit testing and you can get you can learn more about unit tests in in my other videos but really what you want to do is you want to take this out as a dependency essentially you're saying you're always gonna go to date time now millisecond and get a real value what you want to do instead is you want to mock this so that you're not actually returning the milliseconds you're controlling what number gets returned here that way you could try things out like what happens my return one instead of oh oh one does a return oh one probably not probably turns one but mean maybe not because the fact it's a decimal point so you try to figure that out something like that but then you can also control the entire employee ID imagine this was at the start and the end that my cousin problems but in our case is pretty simple we're going to start with what's the starter string look like so let's create a theory here and then we're going to pass in some inline data and then we have our three things that pass in first name last name a expected start so I started something simple so Timothy and then Cori and then the expected start is going to be T I am Oh cor e okay that's we know that works but rest start what we know works so let's test this out now if you're following along the the arrange you know act assert then we already have the arrange because the arrange is where you set the expected but we're gonna set up the the the actual so let's just declare that actually go down here to declare that so we'll say the string actual start equals and then we'll do we'll make a call to our employee processor so employee processor control dot to bring in the demo library we have to have to instantiate as first let's cut this out actually I could be a net that could be a part of the arrange though there we go processor equals new processor perfect is a processor dot generate employee ID with the first name and a last name and then we're gonna get the substring of just the first eight characters okay that's good a start and we'll we'll figure out why it's not good overall in just a minute and the search is that we can do a equals where we say that the expected start and the actual starch are the same now if we run this in the task Explorer let's go ahead and run all and make sure this works so let's first start by one spin that let's go ahead and build our solution and I forgot something here that's what happens every once in a while you forget something in this case I forgot to install the X unit dot Runner and a couple of runners who want install the console install that one and will also install the visual studio runner as well and that just tells visuals to do how to run these unit tests and once that's done we should be able to run all and there we go now we have over here a running unit tests now and this is where we want to save some time instead of running your application getting to the right spot I was really easy with its application because of the fact that it's it's right there in the opening screen and we actually know that the bug - but it's right there in the opening screen so it's not hard to set up but imagine this was three or four layers deep maybe you have this you know it's complex wizard builder system where the user goes through and puts their you know their first and last name in and then they go ahead and put their address in and then their bank account information that a credit card information whatever and you get to the end and that's where the bug is try to recreate that every time is a paint I know I've done it I do it every once in a while and so this right here is gonna save us some time because instead of doing all that setup process to test out something else I just do this and let's just say I'm gonna expect that this is going to be this which I dunya changes unit test let's go ahead and unpin this I'm doing a substring eight that's not really right let's say that the substring should be the the eight instead of eight it would be expected start dot length they the same length is what you expect so we run this again with a second theory we get a broken unit test okay one worked that's the first one the second one breaks and if we bring up this here it says index and or I am and length must refer to a location within the string it's a basically it's out of range exception or and we're trying to go four characters into a three character string that's what it comes down to so now we've found the bug which doing it this way allow us to do it quickly and allows us to keep this around to test future versions or future fixes so now you can come over to our employee process and say let's see how to fix this well one way to fix this I think the best way to actually create a new private method private string get part of name let's call that and we'll pass in the name we want to get part of now I could keep creative method just as it is but I want to point out one more dry thing and that is number of characters instead of passing as magic string here for our magic number I want to say hey you know what down the road might change this might say five instead four in a different system maybe the email system or it generates the email address for the user the new user we might say one character from the first name and all the characters from the last name so in that case we would just say one here for a number of characters now you can do substring as well but now we have this that's a little more robust hopefully it's going to be a little more robust in which case we want to pass it through this instead and so now we have the system where we can reuse this private method in this employee processor in more than one spot too so again we are keeping ourselves dry by not saying it's always me for characters or it's always me first name or last name so here let's say that the the string output let's start off as being the let's start with an epi string let me just say if the name dot length is greater than or equal to in fact you know what let's start it off instead of being an empty string let's start off as name and then say if if name dot length is greater than number of characters then we're going to do is we're we're going to say outputs equals name dot substring start at zero and say number of characters so if the name is Tim we're going to put Tim and output Tim dot length is three three is not greater than four therefore we're not going to do this code but if Timothy is passed in Timothy's got length greater than four therefore we're gonna say is Timothy just to the first four characters therefore it's t IM o goes into output either way return output and now we can say get part of name and just say give me four characters of it like that there we go and now we're using this method instead so if we don't have four characters it's gonna return however many characters we do have so in the case of Tim it'd be three characters now if we come back over here and run our tests again and say well that's actually we expect a seven carries eight so let's run our test again and see we get four results and now we have passing tests just to verify let's go ahead and say that in fact my last name is C oh just timco and that's what it should return let's run that again and this time we should have and we do three passing unit tests but you see I did there I cut down my development time in my debugging time and my testing time by not repeating myself I'm not starting the application up type the information in hitting the button see if it crashes seeing if the results are right and then going back and starting over and trying a different result this right here is reducing or eliminating my repetition by saying I'll try it for you over and over and over again and now if I come over here and say you know what I'm thinking that instead of saying well I'll just you know return Tim instead of TM oh because we only have the first three letters I think we should do is we should return TI em and put an X at the end an X for every blank space well we could write that code and we could say you know what yeah if the length is less than four what we can do is do a do loop and say while name dot length is less than four and here we can say name plus equals and we'll put an X at the end now that's not the most efficient code because we are appending a string and if you go to that a thousand times they'll be very inefficient or you know a hundred thousand times but for our case is probably not a problem and it's not worth the extra overhead to try and write something more efficient we actually take more work to do that just kind of pointing that out there but so this case we write append X to the end well first of all just doing that change which the boss might want is going to break our unit tests okay let's just see that right now it doesn't that's interesting so we gotta figure out ah I see why did I add a name isn't it awesome my unit test just said no we're good you you're getting the output you expected originally which was timco not TI m XC oxx so i know right away i got a bug in this code so our because that I just wrote but it's not do I wanted to do and the reason why is because I want output not name to be appended to now let's run again and hopefully should break on me and this time it does to a three broke well Y one pass well one is the full working copy but now I say well the boss actually wanted to look like this oops not cor a is fine but t I am and the X there and a CO and two x's let's run this again and make sure that it in fact works the way the boss wants it to and it does I didn't have to open up my UI and run it and type in the information and hit the button to make sure it worked so I've cut down on my code but I've also cut down on all the work that goes around with writing that code and testing it and making sure it works and fixing the bugs and making sure that bugs aren't really fixed and so all that is accomplished by writing a really simple little unit test and so this right here even though it's more coding has actually saves me a lot of work both now and in the future so this really does fit under the DRI principle we're not repeating ourselves but it's not about code this time it's about our testing and debugging we're not repeating our same tests over and over again by doing it ourselves we've written it once we've done the test once and now we just have it automatically run or I click the button okay so that's yeah another way we can cut down on our repetition has not a whole lot to do with our code but it has to do with how we approach our code and that's where unit testing comes in but we're not done yet one more thing I want you to consider work we talked about creating a library this library right now takes care of a winform UI a WPF UI and then a different solution a console application that's great work across all these different user interfaces and it'll also work against asp.net MVC but we're not covering all we could with this library and so there is a repetition issue that may we can solve and that is if we right-click on here and say add new project instead of create a class library that says dotnet frame if instead we create a class library called dotnet standard then well happen is our code will be not only usable on windows with although the you know winform UI and the WPF and console and asp.net but it will also be able to be used by the dotnet core apps it'll be able to be able to be used in xamarin which is the the friend tooling for working on iOS and Android and Linux and Mac and so now our code not just doesn't just a our repetition for on Windows it also saves repetition on these other platforms so if you can do it create a dotnet standard library and it looks a little bit different but not much okay so I'm gonna go ahead and delete class one instead I am going to create a new class and we'll call this the same name let me get public and check this out I'm gonna grab the code from our old employee processor just copy and paste and yes copy and paste is UD bad but in this case we're moving it over to a new platform and so there we go so that's really all I did and now we can call this dotnet standard library from wind forum from des PF from asp.net but also from xamarin then also from dotnet core and so now our code lives in more places so we begin cut down on that repetition by allowing us to go to more platforms than just windows so hopefully you've seen that we started off we started off with a code right behind this and while it didn't necessarily have much repetition in the code itself it caused us to repeat ourselves when we create new applications or new functionality in this application and so by moving into a method we can call to get on this form but then moving into a class library like we did here allows us to call it in the WPF user interface it allows you to call it in other places by taking that dll to the new solution but then we can take a step further by creating instead in the.net standard which allows us to go to other platforms as well but then also we looked as well we're not only repeating our code work but we're also repeating our effort in testing and debugging and that's we brought in the unit tests so we can run those tests and try out those things in automated fashion so that we're not repeating our work as developers by launching the application running through the process we can test it out by just hitting the test button there's a lot of ways we can approach keeping your code dry it's not just about saying oh I'm writing the same exact code more than once but that is a valid area it's just there's so much further we can take this to make sure that we don't repeat ourselves now the question may come up how far do you take this and do you always go to the dotnet standard and do you always write unit tests and do you always do these things and the answer is not always and that's one of the things when I caution you is you need to create for your situation but still remember the future so here are my general tips and tricks when it comes to this type of dry don't put code in the code-behind don't put code in the user interface at all if you can help it so this type of code we have here this code this does not belong in the user interface there's no reason to put it there because we can genera size it enough so we're not talking about you know this text box or this event or this button we don't have to know that we just have to know you passed in information and then we pass back other information or process to information so this we shouldn't live in the form project or whatever user interface you have because it's much much easier to take code from a library and reuse it and you may think I'll never need it but more often than not you will ok and if you're never gonna need a code again I'm concerned about your application because it's either really really small or you're not thinking long-term enough or it's a throwaway application because applications need to grow over time and part of that is you may need to change your user interface so whenever possible it's well my one of my keys is where possible put your code into a library now as the dotnet core has evolved I now recommend whenever possible put your code in a dotnet standard library and the reason for dotnet standard library is because you can take advantage of all the platforms that c-sharp offers when c-sharp offers platforms across the board so if possible put your code in a dotnet standard library now there is some code that will not fit in there for example if you are working with text files on disk or the config file these are things that won't really fit in a dotnet standard library well encourage you do then is create two libraries one that is your your standard library your dotnet standard library and that's where most your code lifts the code that can live there the code that can't live there create a separate library there are ways you can in your dotnet standard library has some workarounds I'm not a big fan of those that you can avoid them so it might be it's probably simpler to create two libraries and that way the code that you can use other places you have access to and the Cody couldn't use other places anyways it's still a library so it can still use across Windows just not across Mac Linux iOS or Android so that's my encouragement as far as unit tests I always encourage you to look into creating unit tests because like you saw unit tests allow you to really speed up your debugging process and you may think you don't write bugs you do okay just in this demo I wrote bugs now some of those were intentional but some of them weren't and so being able to have that unit test back you up and say yes your code was good saves you a ton of time and a ton of worry because right now I am not worried about making changes to the generate employee-id method if you asked me to do something different so the boss came in said you know what I want to put the X at the beginning not a problem I will modify my unit tests to reflect what the boss wants and then a little break and then I'll change the code in here to where I think it should be and then we'll try it again and the unit tests all pass I'm good to go I don't have to worry about well I should launch the application try it out I don't have to do that because the unit tests have done it for me they have run through my code and made sure the inputs the outputs match what I expect them to be and that's really all I need to do so I do encourage unit tests now there is a size level the e Nia think through if you're creating a very small application or a test project or some bit of that nature you don't have to create a separate dotnet standard library and then maybe a.net framework library as well and unit tests and all this stuff for it really is a simple test project or a proof of concept so I don't recommend it for that for a small little project that you're use in production I still recommend the library and the unit testing and the reason why is because typically what happens is small little Production apps grow over time and that's where you have a lot of our larger software heads come from small little production apps so I'd recommend getting started on the right foot with anything that goes into production ok so that's my recommendations I hope you've enjoyed it like I said that this links are in the description below one for the sponsor this video PBS studio I want to thank them for sponsoring this video but also a link to the blog post that has the code at the start of this demo and also at the end so you can kind of see what I did but also try it out for yourself ok if you have any questions please leave them down the comments below I try and get to all comments and respond if possible thanks for watching as always I am Tim quarry [Music] you [Music]
Info
Channel: IAmTimCorey
Views: 91,842
Rating: 4.9530849 out of 5
Keywords: .net, C#, Visual Studio, code, programming, tutorial, training, how to, tim corey, C# training, C# tutorial, dry, dry in C#, don't repeat yourself, dry software, dry design principle, dry design, d.r.y., c# tuning, iamtimcorey, design patterns, design patterns c#, dont repeat yourself, .net core, .net developer, programming for beginners
Id: dhnsegiPXoo
Channel Id: undefined
Length: 53min 20sec (3200 seconds)
Published: Mon Oct 22 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.