Тёмный

One Simple Trick to Instantly Improve Your Code - Finishing Tom Scott's FizzBuzz 

bit-Machine
Подписаться 1,9 тыс.
Просмотров 74 тыс.
50% 1

See the follow up video about addressing some mistakes: • Is My FizzBuzz Solutio...
In this video, we are taking a look at Tom Scott's solution to the FizzBuzz challenge, and see if we can improve his unfinished code.
The original video: • FizzBuzz: One Simple I...
bit-machine.co.uk/

Опубликовано:

 

21 окт 2023

Поделиться:

Ссылка:

Скачать:

Готовим ссылку...

Добавить в:

Мой плейлист
Посмотреть позже
Комментарии : 326   
@bit-machine
@bit-machine 8 месяцев назад
Here is the follow up video where I address the issues raised in the comment section: ru-vid.com/video/%D0%B2%D0%B8%D0%B4%D0%B5%D0%BE-qhKeu83Lay0.html
@rubinelli7404
@rubinelli7404 8 месяцев назад
The problem with abstraction is assuming you know where the code is most likely to change. Way too often, that added layer of abstraction adds work when a new, non-trivial requirement is added. For example, say we add the following rule: "When the number is divisible by 20 but not by 3, print Bingo instead." Now this is simple to add in a "dumb" if-else chain, but not as simple in the code that abstracts out the rules.
@mihai-alexandrusoare9628
@mihai-alexandrusoare9628 7 месяцев назад
You could solve it by making another function fizzBuzzIf that takes a function instead of integers and keep the old one as an adaptor for the new one. That way you keep the simplicity of the old api, while having a new function that satisfies the requirements. I agree that code like this may be harder or more time consuming to write, but it is easier to use and document it this way and I think it pays off in the long run.
@daemonsoadfan
@daemonsoadfan 9 месяцев назад
I personnally would have separated the limit variable than the rest of the ruleset. The reason is that, when manipulating the object rule set, with your solution you need to know in advance than the first element contains this type of object, but the next elements are different types of objects. So i would have preferred two variables with meaningful names, instead of only ruleset :)
@TheArrowedKnee
@TheArrowedKnee 9 месяцев назад
Yep, i would've prefered to just have the limit as a separate parameter to the function, but otherwise this isn't bad.
@xbylina2641
@xbylina2641 9 месяцев назад
Exactly. If you have to include limit into the ruleset, the much better way would be something like: ruleset = {limit:100, rules:[{number: 3, word: 'Fizz'}, ...]}
@user-yc9rp8mv5b
@user-yc9rp8mv5b 8 месяцев назад
Visit #solvemyprogrammingtask
@Templarfreak
@Templarfreak 8 месяцев назад
in other languages this is not necessarily a problem because some have more clear and concise ways to organize the data in the same way without the issue you mention. for instance in Lua, you can simply go ruleset.limit = 100 and add your rules like ruleset[1] = {3, "fizz"} and you could even define some other useful values like ruleset.number_key = 1 ruleset.string_key = 2 (though it would be better if these were in some different table, probably the same table you put the function itself in, but just doing this as an example) so you can access it like ruleset[1][ruleset.number_key] as opposed to it being buried a little bit in JSON array hell. this basically accomplishes the same thing you were wanting while still having the advantages of what was shown in the video; it makes the idea of the limit be more exposed, and due to the way Lua works if you iterate in a loop over the table with just integers you will never access limit or number/string_key and by assigning rulesets to indexes like 1 or 2 or 3 you will never accidentally override or access the limit or number/string_key. and doing it in this way also gives you the advantage of keeping the sort of encapsulation shown in the video that keeps limit as apart of the same object but you dont have the issue of stepping on your own toes.
@just_morby
@just_morby 8 месяцев назад
@daemon agree. The solution in the video is a nightmare in TypeScript.
@shadow_vertex
@shadow_vertex 9 месяцев назад
I see you were inspired by FizzBuzz Enterprise Edition
@ratelslangen
@ratelslangen 9 месяцев назад
Major problem is your use of a heterogeneous list for the ruleset. Take the limit out, pass it in as a seperate parameter, makes the function itself much smaller. Add a starting number parameter for bonus points. Return as a list of strings rather than logging it for purity, reusability and maintainability. Personal gripe: two single letter variables that arent very clear, could easily be replaced with a full word or the loop over the ruleset can be replaced with a foreach.
@IvanToshkov
@IvanToshkov 9 месяцев назад
This! Also, extract the function, that given the ruleset and a number returns the string, that corresponds to this number. This function can then be used with the loop, but also with some stream of numbers. (To make this even more functional, create a higher order function, which takes the ruleset as a parameter and produces a function that takes a number and produces the string).
@harriehausenman8623
@harriehausenman8623 9 месяцев назад
Yes! this code is so bad. Even Toms naive approach is better.
@maticz3923
@maticz3923 9 месяцев назад
the variables i and j are clear those are common names used in for loops
@fg786
@fg786 9 месяцев назад
Does anyone actually bat an eye over single letter variables when their scope is clear by being defined as an iterator in a for loop that is 2 and lo and behold 5 lines of code long? In a longer loop that spans more than fits the screen or is nested with other loops you might have a point but with this complexity and simple scope, why bother?
@9nikolai
@9nikolai 9 месяцев назад
@@fg786 Readability is very useful when coming back to a code months later trying to change something small enough to not grant a whole rewrite.
@JasonLatouche
@JasonLatouche 9 месяцев назад
I think this solution is kinda nice for a more complex set of requirements... For example: the ruleset can be updated live from a database without the need to compile/deploy your code again...
@matthias916
@matthias916 9 месяцев назад
yes but what if it doesn't need to ever be updated. you'd end up having wasted time adding a database migration and writing this complex code when Tom's original code worked just fine. imo this video is refactoring prematurely but if you did need to add checks regularly then this would indeed be the way to go
@9nikolai
@9nikolai 9 месяцев назад
@@matthias916 The whole point is to try to think ahead. If you're told to make some quick code that does the job first try, then do it as simple and intuitively as possible. But if you're told to make code that can be used years into the future, this is one of the best approaches, if not the best.
@matthias916
@matthias916 9 месяцев назад
​@9nikolai you're overcomplicating things. you don't know what's gonna need to change in the future, you can guess, but you don't know. once something needs to be changed you can change it, until then, just leave it as is. if you're changing things before they need to be changed you're just wasting time because that part of code might end up never having to change after all. when you write something, you need to write it in a way such that it can be changed easily when it needs to be, but until it needs to be changed, don't change it. code that is easy to change is maintainable code that can be used for a long time, so if you're required to write code that can be used a long time from now, write it in such a way that it's easy to change
@9nikolai
@9nikolai 9 месяцев назад
@@matthias916 Except sometimes you do know what needs to be changed in the future. Sometimes it is very obvious that some numbers you're using are just temporary, but you still need to use them. For example, if you need to do a bunch of testing to figure out which numbers to use, then you're gonna have to change them a lot in the beginning, and trust me, it is much easier when you don't have to scan the whole code to find the one bit you need to change. And again, the challenge explicitly states that the point of it is to see if you can think ahead and prepare for change, so you're only wasting time on arguing that "we can't know what will have to change in the future". Besides, the abstracted code can be much easier to reuse for other things by just changing the ruleset. The simple code just checks for whether some numbers are divisible by 3 and/or 5, while the abstracted code is a tool that can check for any factors, and can even be easily adapted to check for prime numbers. At larger scales it is easily the best option for many types of tasks to abstract them. Sure, not every code should be abstracted, especially as it isn't optimised for anything else than versatility, but don't pretend as if an experienced programmer can't tell whether or not it'll be useful.
@QwDragon
@QwDragon 9 месяцев назад
But having the limit in the first element of array is awful. You should've do smth like config = { limit: 100, ruleset: [...] }. Also you forgot to define variable via const/let/var. Anyway, I think, Tom's version isn't worse at all.
@DNA912
@DNA912 9 месяцев назад
I think there a two different ways you can reason when developing code to be future proof. 1. make the code very easy to understand and read so it becomes very easy for someone to understand it and change it later if necessary. (this is toms most compact solution) 2. break it out and abstract away the logic so you don't need to read it to use/change the behaviour. (this is what you did) Both of these approaches are great. In the fizzbuzz problem, I would probably go with toms. But when problems and logic become more complicated. Like, making an algorithm to calculate prices of raw materials based on parameter a,b,c,d,e,f,g.... Then reading and refactoring becomes way to much work and your technical debt with go to the moon very fast, and this approach is better. At my current job I'm working in a codebase that is 20 years old and with a technical debt half way to the moon. and this type of abstraction of logic is so valuable when complexity becomes higher then simple if statements.
@toopkarcher
@toopkarcher 9 месяцев назад
Yeah, this is the problem with trying to come up with fancy abstract solutions for trivial things.
@user-yc9rp8mv5b
@user-yc9rp8mv5b 8 месяцев назад
Visit #solvemyprogrammingtask
@wlockuz4467
@wlockuz4467 9 месяцев назад
This reminds me of my favorite programming principal. YAGNI - "You Are Not Gonna Need It" It basically means prematurely preparing your code for future extension is a bad idea.
@Wishbone1977
@Wishbone1977 8 месяцев назад
Be very very careful about "universal rules". And also, regarding that one in particular, give some thought to what "prematurely" means in this context.
@Sammysapphira
@Sammysapphira 8 месяцев назад
This is the worst rule I have ever seen
@8d-sounds-gab
@8d-sounds-gab 8 месяцев назад
Absolutely agree on. Getting things to work is more important than being optimal at first and it too doesn't mean being as careless as possible about the code design, just focusing on what matters 😁
@beatboy6690
@beatboy6690 9 месяцев назад
I really, really hate this code. Whilst tom's code is not what I would do because as you said it does mix rules and logic, his code was substantially simpler. Lets take 1 example, rule 1 from your ruleset object. Instead of using a variable with a simple name like limit, defined within the function, you have used a very opaque ruleset[0], as a maintainer I have no idea what that is, which means I need to go to the definition, remember what ruleset[0] is, then play around with it in the context of the code to slot it in place of ruleset[0], which is the opposite of how abstraction should be. The point of abstraction is to not need to look at the code to understand how it works. In fact I just have a problem with using the object in this code at all. Here is how I would remake the code, instead of an object create a hashmap whose key is 3, 5... and the value is 'fizz', 'buzz' …. I personally would put it inside the function to prevent name pollution but it doesn't much matter. Then define my limit variable to be say 100, create my first loop and output variable as you have. Then instead of having to loop through all of of the things in the object you can use JavaScript's built in .forEach() function to check if I is divisible by a key, then add the value to the output variable if it is, whilst this essentially does the same thing its far more terse and easier to parse. Then check if the string is empty as you have. And finally console.log(output).
@lilellia
@lilellia 9 месяцев назад
Yeah, my solution would have been to do something like (Python, not JS, but...) def fizzbuzz(limit: int, modular_replacements: dict[int, str] | None = None) -> Iterator[str]: if modular_replacements is None: modular_replacements = {3: "Fizz", 5: "Buzz"} for i in range(1, limit+1): output = "" for divisor, replacement in modular_replacements.items(): if i % divisor == 0: output += replacement yield output or str(i) # could replace with print(output or i) if you just wanted to print instead of returning print(*fizzbuzz(100), sep=" ") # would print them all on their own line
@BrotWurst
@BrotWurst 9 месяцев назад
also the whole code breaks if someone or something pushes to first place, reordering rules, or anything else. its really fragile piece of code. you should NEVER use fixed key indexes of arrays especially in a "better code" video example :D also the var j = 1 is just triggering. and the lack of optional chaining in this case could really throw some errors in the future. there are so many downsides and overcomplications in this code. i can see myself writing something like this about 5 years ago as a junior and thinking "oh well! thats genius" and now when i look at my old repositories i facepalm myself for the things like this :D :D :D
@CallousCoder
@CallousCoder 8 месяцев назад
@@lilelliamy solution would’ve been to use a proper programming language like C, C++, Zig or Rust that has a compiler that can optimize and unroll the structure into a nice jump table. That’s fast all that interpreted stuff is so slow and Python is the slowest. I bet your code is 3 to 5 times slower than the video in this video. Just by the nature of how Python works and how inefficient loops and memory access are in Python.
@RussellEveleigh
@RussellEveleigh 9 месяцев назад
Love the clarity of your approach.
@bit-machine
@bit-machine 9 месяцев назад
Thank you. I'm glad to hear.
@4lve
@4lve 9 месяцев назад
I don't think an array should contain diffrent values
@cahdoge
@cahdoge 9 месяцев назад
You are right, an arry not only shouldn't contain different data types, it can't. The "array", he created is in fact a list, containing multiple dictionarys.
@4lve
@4lve 9 месяцев назад
@@cahdoge Yea, but the objects contained diffrent keys
@BrotWurst
@BrotWurst 9 месяцев назад
@@4lve absolutely! your right that was my first thought. i also wrote a comment. i would prefer something like this: options: { limit: number, rules: [ { word: String, number: Number, }, ], } also i really dont like the fact that he used i = 0 AND j = 1 :D thats triggering me so much. arrays of objects should almost always contain the same "type" of object. also limit itself is not a real rule. you are not checking anything in the loop, you are predefining the length of the loop. thats more like an option or definition of the whole program, not an rule to check the values in my opinion. different usage, different name and location!
@Cranked1
@Cranked1 9 месяцев назад
I would consider your "improved" code to be bad and even worse than Scotts. You made it more complex by adding unnecessary abstractions, made it harder to read and made it slower. You also have to lookup the ruleset array which is outside of the function that does the actual logic if you want to understand the function. Also your solution only works in languages like javascript because you used an array of objects and mixed different array values which is a big nono. Abstraction is not the answer to "better" or "cleaner" code. It is useful when needed but using it everywhere like in the smallest function possible leeds to bad code.
@ratelslangen
@ratelslangen 9 месяцев назад
This isn't substantially slower (bottleneck is still in logging) but it is more reusable and maintainable. Clarity can be solved with type annotations in typescript or another language. If performance was the bottleneck look into batching print statements with a stream. In most situations clarity and maintainability is more important than pure speed, performance critical areas are very few and far between and need different approaches.
@bit-machine
@bit-machine 9 месяцев назад
You're absolutely right. However my aim wasn't to create fast code, or the technicalities of using different array values (even though they are important). It was about showcasing how to write code that is as easy for future users to read and modify as possible. Maybe you're right that some might find this approach even more confusing, but I personally think it's quite straight forward. Nonetheless, thanks for taking the time to give your feedback, it's good extra information for higher level coders.
@OxAA00
@OxAA00 9 месяцев назад
I would argue that it is indeed way too abstract for fizzbuzz. This might help by showing how you can think about abstractions. In most cases though people thend to preemtively try to "guess" future abstraction needs and are actually making it harder to restructure the code when needed. I would argue that it is in most cases better to just have a dumb fizzbuzz like solution and ONLY if there arises a need to tailor your solution to a new extensibility you want to think about an abstraction. Otherwise you will most like have to abstract twice or even worse you're going with the assumptions you made with your earlier abstraction and implement even more confusing solutions. Interestingly enough one most have learned about using abstractions in the first place before seeing where to use them appropriately. I at least learned in my time as a dev to NOT abstract early and would rather throw away my shitty code and let the new requirements design my future changes. Old me will always thank me for this. @@bit-machine
@DarkNeutrino_R
@DarkNeutrino_R 9 месяцев назад
It isn't slower. Realistically the lookup to the ruleset is 1 instruction. And reading a variable is also 1 instruction. There is also the for loop. You literally nested for cycles. The one thing you should strive away from. The instructions for the loop are minimal but they do grow with the size of the ruleset so it's not amazing. So realistically speaking its the same speed unless we go down to nanoseconds. The issue in this "better" code is that you made the function far too generalized. The way scot did it is absolutely fantastic. Realistically speaking nothing to improve upon. And when you need to scale such a function up into 10+ if statements and you are thinking about abstracting it like that i would recommend taking a step back and checking the problem from a wider point of view. Do we need to even generate such a string in the first place. And then if you find a way to avoid it. Compare the 2. Are we making it faster ? Does it cost us more RAM ? Evaluate your target platform. Usually using a tiny bit more RAM at the benefit of CPU speed is a win. TBF I work in embedded dev with the linux kernel. So folks in userspace may look at it differently but this is the way to go about it if we are talking about low level development.
@Cranked1
@Cranked1 9 месяцев назад
​@@ratelslangen now you introduce typescript to make it more clear after making it less clear...so another overhead. "In most situations clarity and maintainability is more important than pure speed,", that's correct, but my answer clearly focusses on maintainability and readability. Performance is not my main argument. "it is more reusable and maintainable", you stated that, but without any explanaition. I would argue that splitting such a simple logic problem into two areas by abstracting the actual logic is not worth it and hence a bad example for a video called "One Simple Trick to Instantly Improve Your Code". That's my problem with this video. Abstracting is fine when done properly.
@just_O
@just_O 8 месяцев назад
People that argue it's an overkill for such a small problem and the solution made the code "worse" in your opinion, you are right. Also, you completely missed the point. When you explain a complex idea to someone you try to come up with the simplest example to which you apply that idea, so people focus on the idea not the example, take the concept, and try to apply to more complex scenarios, and with practice the idea will become part of their knowledge. If we have to come up with a complex example to illustrate "fairly" every concept we try to teach it would take us more than the lifetime of everyone that watched this video to explain the simplest things. Not to mention how easily people get bored! Don't take my word on it, try it yourself, take the most elaborate example you can think of and try to explain even the simplest of ideas to someone and see how long it takes before they run away
@AlbatrossRevenue
@AlbatrossRevenue 8 месяцев назад
I think you're misunderstanding the (constructive) criticism from others. It isn't about having the "most elaborate" or "complex" example. It's about practicality and adequacy. The example should match the practicality of typical circumstances that the solution should be applied. Take the original example of just fizz/buzz. The solution matches the example well. It's enough to demonstrate the importance of clarity + conciseness. This video tries to tackle an arbitrary ruleset. And while the solution does demonstrate the type of abstraction the creator wanted to teach, the example does not suit it very well because the simplicity of it doesn't lend itself to understanding how/when to apply this particular approach. A new programmer might think this solution is better than the original regardless of context, or might be unable to see an opportunity to apply the same logic when faced with a real world situation. In practice, such a solution is more likely to be applicable if you're dealing with a large or non-static config/dataset, or arbitrary user input where any number of rules can apply. And furthermore it's best suited for circumstances where the logic that dictates the structure of a rule is unlikely to change. The video didn't distinguish between future-proofing and overengineering. That's a problem I'm sure many seniors have encountered when dealing with new recruits, which might be why many took issue with the example. If I introduce a new rule that if the number is a factor of 2, output "wazz" and _do not output anything else_ then it's obvious how this solution created a box that an inexperienced programmer may struggle to get out of. In many cases, they'll try to fit the change in logic into the same box because they learned "that's the right solution". And that might not be too bad at first. But then I also say, if the number is a factor of 3 and 7, output "razzmatazz" and then _only_ apply rules where the divisor is greater than 15. Now what? The loss of flexibility isn't readily apparent from the video, hence students might not understand when this solution is appropriate. They might be inclined to think how they can fit logic adjustments into the solution rather than architect a new solution. Granted I could argue this is all part of honing one's skills, and making mistakes is important. The bottom line is this solution is predicated on expanding the rules, but only expanding them to a certain extent and in a certain way, and the video did not address that. To more experienced developers the intention is obvious, yes, but newer ones could easily misunderstand, and they're the target audience. An example tailored to this solution would both enhance the understanding of students as well as help the creator identify caveats before publishing it so he could raise them in the video.
@yourmomsboyfriend3337
@yourmomsboyfriend3337 8 месяцев назад
@@AlbatrossRevenueperfectly explained my point and a few other points, thank you. Hope the original creator reads this
@nan0s500
@nan0s500 8 месяцев назад
@@AlbatrossRevenuethis is such a good response man. You captures everything in a very clear way.
@Sammysapphira
@Sammysapphira 8 месяцев назад
Teaching bad code because people "get bored" is probably the dumbest thing I've ever heard of. Next we'll teach the lore of Shrek in math class because people "get bored"
@aaronspeedy7780
@aaronspeedy7780 9 месяцев назад
No disrespect to your content, but this seems like a way to instantly over complicate a lot of your code. When reading this code, you would have to jump to a new function to see how it worked, and in order to change the anything you would have to jump to the definition of the rulset and change it there. It's also pretty limiting in that you would have to change how the ruleset data is stored to change the logic. I think you have the right idea of storing the conditions in a data structure, but I think a much better approach would be to just store it in an array defined inside the function and only turn it into a general function if you need to do the same thing elsewhere in the code
@MrBreakstuff
@MrBreakstuff 9 месяцев назад
The only issue i really have here is nested for loops paired with "computational thinking". This isn't computational thinking. You're deliberately avoiding thinking about what the computer is doing. This would be thinking in terms of abstraction, which usually goes in the opposite direction - optimising for ease of use to a developer rather than the CPU. Others have pointed out that this is probably only nanoseconds slower, and I suspect that might go away entirely after V8 has run it a few times. It's definitely a good approach, but I think we should be very careful about mistaking abstraction for computation.
@CottidaeSEA
@CottidaeSEA 9 месяцев назад
Assume you need to have different cases for different customers. How do you write the code?
@MrBreakstuff
@MrBreakstuff 9 месяцев назад
@@CottidaeSEA so the thing here is that computationally this is one function with static inputs. If you want dynamic inputs that changes the thing the computer is actually doing. This abstraction should play well with database, JSON, or even config files, so it's probably good, assuming the rule set stays small. Abstractions let us separate different areas of code so we can modify them faster (as shown in the video) or just make complicated ideas easier to digest. In this example you're asking us to guess at the interface used for customers to modify their rules. All we know right now is one set of inputs, which are hard coded. If we add an i/o or configuration layer to handle different rules, that would be best solved when we know how the rules are getting to us, not when we're just guessing at an abstraction that hypothetically makes this more extensible. Otherwise we're basically adding functionality that hasn't been asked for and our abstraction might not actually represent the desired behavior whenever it is requested. Premature abstraction happens all the time and is almost as bad as premature optimization. For faster delivery and execution, don't do more than the feature and safety demand.
@CottidaeSEA
@CottidaeSEA 9 месяцев назад
@@MrBreakstuff You didn't answer the question.
@MrBreakstuff
@MrBreakstuff 9 месяцев назад
@@CottidaeSEA I did. I said this abstraction probably works pretty well for most cases but you really can't write code based on a what if without specifics. If you only needed to map a customer to a set of rules, you could probably rewrite this to take customer as an input and return the list or array. If the user needs to be able to set the rules at runtime, that's another problem. If you wind up with a customer that has 100 rules to handle 1000 inputs, that outer for loop is gonna look pretty silly as well, so you may want to look at your input actually generating functions that get called by a lambda.
@CottidaeSEA
@CottidaeSEA 9 месяцев назад
@@MrBreakstuff No, that really isn't answering the question, you seem to be avoiding answering it directly. You're basically just saying "I would collect requirements from the customers" and that's about it. Essentially, what I am getting out of this is that you'd have the same implementation, you'd just pass the rules (or user) to the function. That is a perfectly valid way of doing things, but for someone who disliked the nested loops, you're really avoiding solving the very issue you mentioned.
@devd_rx
@devd_rx 9 месяцев назад
bro got schooled for trying to Tom 😭😭
@charliesta.abc123
@charliesta.abc123 9 месяцев назад
The limit field definitely does not belong where you put it. Now I would still need to read the code and figure out how that is used
@thisguy.-.
@thisguy.-. 9 месяцев назад
I mostly dont agree with what is presented. This is useful if you are exposing a front end that any non-techie client can use to make changes, but otherwise only adds more redirection and abstraction than necessary. If you're reading the source code to make changes, you have to pick apart even more code thats harder to read and comprehend, especially since the data is stored in a seperate area that you have to look up and find. And generally, while it is good to teach lessons like this in small examples, its more of something that should be saved for when scaling up when the code becomes too complex for someone outside it to read.
@Treeniks
@Treeniks 9 месяцев назад
If you're writing a library, it is an absolutely reasonable and necessary abstraction to make. And the client is then not non-techie people, it's other programmers using your library. Or if you need to run the FizzBuzz function in different areas of your application with different rulesets. You're not going to want to copy the function for every ruleset you need.
@thisguy.-.
@thisguy.-. 9 месяцев назад
@@Treeniks that is if it is infact, a library. The prompt tasked the programmer to create something that will print to an output, instead of returning any kind of value, so clearest course of action states that this is a program and should be treated as such, where others shouldn't have to use any of these programming interfaces for anything other than this simple program. Nobody should be interacting with this code unless 1. a change to this program is necessitated or 2. someone wants to adapt snippets of the code to build their own things with it, in which more inline functionality is better anyways. Plus generally this was just supposed to be a simple lesson about problem solving, rather than long term coding principles. That's how I feel about it, anyway. Also forgot to mention, but the scope of the application is just the output fulfilled here, and nothing more. You shouldn't have to worry about how your function will be run in other areas of the application, because it's literally just the entire application.
@Treeniks
@Treeniks 9 месяцев назад
@@thisguy.-. FizzBuzz is a hypothetical prompt, not a real one. While it was originally created as an interview question, in the context of the video it exists for educational purposes and as such can be very well interpreted as a library function. Change the print to a push into a vector and return the vector in the end, but that is a detail not relevant to what is being taught. If it's a binary you're working on and it only needs to be called once, then Tom Scott's original solution is just fine. But that's why it's a hypothetical prompt, what if it isn't. What if it's a reoccurring output, but with different rulesets every time. The point is to teach abstraction, not to talk about the usefulness of an over-engineered FizzBuzz function. And you can't exactly teach abstraction with examples that are too complicated to understand for beginners in the first place, so you have to use toy examples, which FizzBuzz is.
@thisguy.-.
@thisguy.-. 9 месяцев назад
@@Treeniks If someone wanted to teach abstraction, then they could have done so much more by using a better example. Tom used the BizzFuff interview question as the example for how to solve problems, in an interview setting, not for how to do abstraction. If they wanted to take Tom's example and use it to teach abstraction, they could either have implicitly used it in that context, else they'd have to explicitly take their lesson out of it. Just saying "this is how to make your code *outstanding*" doesn't change the scenario because it's still tied to the context of trying to impress the hypothetical employer, not suddenly because what-if someone else has to use this in a library which was never a part of the discussion in the first place. If they wanted that, they would've put it in the video.
@wardibald
@wardibald 9 месяцев назад
While this is indeed a better way of going about solving this problem with separation of concern, do realize that by decoupling the rules from the logic, you always have a bit of extra work that otherwise would not be necessary: validation of the rules. The code might not behave as expected with, say, a zero or negative or null limit, no rules, repeating rules, null-string in some or all rules, and so on. This is no longer under your control if you decouple the rules and allow future users to access and modify them.
@harsha1306
@harsha1306 8 месяцев назад
Fizzbuzz enterprise edition is what perfection looks like 😂
@ujjwallimbu2272
@ujjwallimbu2272 8 месяцев назад
Im just starting out as a web dev, this seems so brilliant. I literally have to pause and understand for every logic within the code. I hope i will get there someday.
@puzzled3d
@puzzled3d 8 месяцев назад
that’s because the code is bad
@NuncNuncNuncNunc
@NuncNuncNuncNunc 8 месяцев назад
Tom's solution is better - it is easy to read and it is correct. This code is not east to read and can easily be made incorrect by screwing up the ruleset order.
@ujjwallimbu2272
@ujjwallimbu2272 8 месяцев назад
@@NuncNuncNuncNunc really? Well i guess when you are starting out, all the tricks that you have difficulty understand at once seems to be because of lack of knowledge and IK it’s that too. Would you mind showing me what would an optimal code would be for values that aren’t constant? I asked how good this code was compared to tom’s solution in Chat GPT, it just said constant for tom and not constant for this vid is preferred .
@ImForGames
@ImForGames 2 месяца назад
To me this seems like a reasonable approach. The function itself runs using the same logic tom used in his latest code, and adds to it by abstracting the different rules into a new data type (this one is named “ruleset”). The benefit is that the new datatype can be modified in one location and the function accommodates these changes to the data assigned in the “ruleset” data type. The different parts of the ruleset are accessed by the “.” operator. It may seem more opaque if you arent aware of the ability to create your own data types but, this is still a neat solution. This is not an obscure technique either.
@ThyTrueNightmare
@ThyTrueNightmare 8 месяцев назад
as you were showing tom's code, I figured out the solution myself. So I guess I am actually a decent programmer after all!
@ifstatementifstatement2704
@ifstatementifstatement2704 8 месяцев назад
The trick to improving your code is seeing examples of good code, understanding it and doing the same.
@mantisgaming
@mantisgaming 8 месяцев назад
I like the concept that you demonstrated. This is seen in a lot of Javascript packages with options parameters to various functions and I love how you described the original code as being attached to the parameters. That being said, don't pass an array of different types. Pass an object that contains the array in addition to the general data. Sidenote: I dislike that you didn't use semicolons lol (also your indentation of the list initializer hurt my soul).
@karolworkowski3261
@karolworkowski3261 8 месяцев назад
A lot of people here are lacking the understanding how to create stable and managable code for the user/developer. The ruleset idea is good one and it works well with O in Solid. For those people : you need to create code not for yourself, but for others to maintain/understand.
@Petticca
@Petticca 3 месяца назад
This video was great! I know pretty much absolutely nothing about coding. I can, however, follow concepts and learn, I think. Heh. I watched the original and I appreciated that Tom started by breaking down that you're actually asking for different things to be done, and that you need to understand what and why. Which was really helpful for someone like me, a viewer who is pretty ignorant on the subject, it is filed under basics, after all. I think it was the perfect amount of information for the intended audience. Watching this "follow up", was fantastic for explaining why you wouldn't actually code it that way if you were using it for anything more than an ad hoc demonstration that you understand _what_ you're being asked to create, and what separate concepts you need to understand are included. I think if I followed you correctly (again, ignorant peon here) that primarily your changes place the information that's uniquely specific to the game in question, in a clear, easy to access and thus change, location. And in doing so you streamlined the code? Is this set up now actually performing/executing (sorry I don't know the correct terms) less commands, as it's using one and running through a checklist that includes 'instruction' there? I guess I'm asking is are less "if" statements within the loop because they've been consolidated into the code at the top? If yes, is this more efficient and less resource intensive? And if that is the case, then presumably in more complex programming/coding there would potentially be times where the complexity, or specifics of what you're asking it to do, would be better kept as separated commands? I hope someone with knowledge can understand what my ignorant ass is trying to get at and help out, it is would be appreciated!
@warisulimam3440
@warisulimam3440 8 месяцев назад
You mentioned "Basic Elements of Computational Thinking". Could you suggest some resources I could look into to dive deeper into that? Thanks!
@bit-machine
@bit-machine 8 месяцев назад
Not from the top of my head, but I'm sure you can find books or videos easily.
@ablobofgarbage
@ablobofgarbage 8 месяцев назад
Why have the limit in the ruleset? It could just be another parameter to the function
@kurdm1482
@kurdm1482 9 месяцев назад
subscribed, and liked the video thanks for reminding me of that mindset!
@aCrumbled
@aCrumbled 9 месяцев назад
limited memory? what’s that? memory safety? never heard of it. over complicating a simple problem? i’m an engineer, that’s what we do!
@CallousCoder
@CallousCoder 8 месяцев назад
Then that engineer didn’t study engineering 😂Because when I did EE the mantra was “only stupid engineers over engineer, that’s who Germany lost the war 😂” But it’s easy to over engineer it’s really hard to engineer to operational values. I’m no civil engineer but I am sure I can design a bridge that works. But it will be very expensive and heavy when in doubt “add more steel and just to be sure also add extra concrete to it”😂😂😂
@VioletCatastrophe
@VioletCatastrophe 9 месяцев назад
I've been doing something similar in a personal project of mine. I have a number of objects all following the same interface (TS), all of which get interacted with in the same way. The main loop that handles the gamestate contains zero data, and refers to the object of objects containing the data. The loop as a result, is exceptionally clean and easy to maintain, and equally any parameters or functions related to those objects are stored within the object as props and methods; so if I want to change how the game iterates the gamestate I can check the main loop of logic, and if I want to change what individual features in the game do, I change that feature's definition. While I agree with some commenters that doing this in this example is overkill, and that decoupling the ruleset from the logic has no major advantage here and actually makes it harder to read (and also that the array of mixed types is bad mojo)... I disagree with the ones that say this should be rarely done.
@saycrain
@saycrain 8 месяцев назад
this is great for modifying code in the future. something I do aswell with code that’s still changing a lot
@Lampe2020
@Lampe2020 9 месяцев назад
This is roughly what I do for most of the more complex functions in my own JS "framework" (not really a framework, more a huge set of small functions to make my life easier by not having to worry about details like "How exactly do I do a resource fetch()?", or "How the heck can I make my async code wait for x milliseconds?", etc.).
@veqv
@veqv 9 месяцев назад
please never write code like this.
@matthias916
@matthias916 9 месяцев назад
ok but what if i want to not just check whether its divisible by x but also by 100 + x? or whether x + 10 is divisible by 33? youd probably use lambdas. now what if i want the ruleset to be different for each client? or configurable by clients? youd have to store the ruleset in a database. youd end up with a rather complex piece of code. thinking ahead is great, but dont think too far ahead, id say that toms code is perfect because if we ever need to expand this code we can simply rewrite it.
@David_Box
@David_Box 9 месяцев назад
And what if the client then tells you to throw away the whole division thing and to start working on something actually useful? Of course requirements change over time, the point is for you optimise for what you reasonably think you will need to do. If the next thing you're going to be asked is to add another 100 numbers to check against, witch is a perfectly reasonable assumption given the context of the interview question, this is an entirely valid refactoring.
@matthias916
@matthias916 9 месяцев назад
​@David_Box I'm not sure you understand what I'm trying to say. I'm saying the code in this video is overcomplicated imo, unless the client actually asks for you to check whether x is divisible by other numbers, you dont need this code. Tom's code is fine imo since nobody currently needs more checks than 3 and 5. You shouldn't write code like in this video unless you're asked to add more checks, only then should you refactor, this is premature refactoring.
@David_Box
@David_Box 9 месяцев назад
@@matthias916 I understand that troughout development you may create features that you may never use and that is bad. What I'm arguing is that you will in fact need to add more numbers in the future (in this particular case) so you might as well plan big. The whole point of the interview question is to see if you can plan for features not explicitly asked for. Even in the original Tom Scott video he basically said this. If you do not need to check against any other numbers, then the first variant where you check if it's divisible by 15 is actually better since a simple check is faster than string concatenation in most cases, but that is obviously a bad way to write code unless you really know you will not need to check against any other number.
@matthias916
@matthias916 9 месяцев назад
​@@David_Box you don't know whether you need to add more numbers though, and even if you think you do, writing the code to support that in this case is just a waste of time. refactoring fizzbuzz so it supports more numbers takes a couple of minutes, why would you do it now when all it does is make the code harder to read. this is premature refactoring
@matthias916
@matthias916 9 месяцев назад
the point of fizzbuzz is to see if you know how to write code at all, not whether you can design a complex system or think ahead, the question is way too simple for that
@Takyodor2
@Takyodor2 7 месяцев назад
I think I prefer Tom's solution, at least until the problem goes out of hand (10+ rules or so). My belief is that premature optimization applies to readability, maintainability and other aspects just as much as performance. Three or four if-statements are really easy to understand and maintain, but also to refactor if the need arises. I would probably save the "is divisible by" variables first at the top, to avoid the "find all 5's"/repeating myself problem...
@NotAHomelessGamer
@NotAHomelessGamer 9 месяцев назад
How did your code work when you had === at line 11? Shouldn't that have been a compiler error?
@bit-machine
@bit-machine 9 месяцев назад
In JS === checks for the same value of the same data type. Good practice if you know your data types will match (int, int in this case). == would return true for 3 == "3".
@jamesfoo8999
@jamesfoo8999 9 месяцев назад
What's with the dodgy "dataset" array that I can't manipulate as a caller of your function? I want to use your functionality but I want it 50 times. And on numbers divisible by "3" I want it to say "Fazz" instead. If you just pass in params to your function with default values the function becomes much more usable and arguably then actually worth the effort of your refactor. Otherwise I'd say in your code review "why did you do this, it wasn't buggy or terrible before. What benefit have you introduced".
@sundown456brick
@sundown456brick 9 месяцев назад
really nice video, not much to comment, pretty straight forward only thing id recommend is making the font bigger so it is easier to read what youre writing, the output and alikes great video
@bit-machine
@bit-machine 9 месяцев назад
Thank you for your feedback. I'll take it on board.
@cahdoge
@cahdoge 9 месяцев назад
I think you could, should have expanded on the idea of good code a bit, using the fizz-buzz example. Since good code is a) always commented properly and b) optimized for it's task. Do you optimize for, runtime, memory, ease of administration (like in your case) or something entirely different?
@matthias916
@matthias916 9 месяцев назад
well written code doesn't need comments unless it's some public api. if you use enough functions and name them properly any competent programmer should be able to tell what the function does just by its signature, if that's not enough, you can take a look at the body of the function, which should be short and concise
@kevinfleischer2049
@kevinfleischer2049 9 месяцев назад
@@matthias916 I agree. Use Comments sparely. Each comment is a potential place of rot, where the "description" will start to diverge from reality. And noting is worse than a misleading comment.
@AJMansfield1
@AJMansfield1 8 месяцев назад
This is almost a textbook use-case for a closure. Define a function that takes the list of pairs and returns another function that takes an integer and returns the corresponding string. The caller can then use this function within any loop they like and do anything they like with the output string, instead of having the 'monotonically ascending integers' or 'console log output' behaviors baked in.
@rilauats
@rilauats 8 месяцев назад
One technique you use but don't name is dependency injection. Your ruleset is injected into the code via a parameter. Decomposition -- or divide-and-conquer if you prefer terms from other disciplines Dependency injection -- or avoid-hardcoding Single responsibility -- this would actually make me decompose the ruleset into (limits) and the divisor-phrase dictionary as separate parameters because they have different reasons to change. Clean code naming -- let the purpose of each function/parameter/variable define its name -- no need for comments unless your code cannot explain itself Still, I like you take on the FizzBuzz challenge to teach us all good programming practices! THX
@another_coding_channel_argh
@another_coding_channel_argh 8 месяцев назад
Brilliant
@randyclark7433
@randyclark7433 9 месяцев назад
Why is the limit in the ruleset, though? You ended up hardcoding the indexes, which means that you have to trust the user to code the ruleset correctly. Granted it’s not that big of a deal for a simple application like this, but if someone adopts the same design for a more complex function it would be a mess. Instead make a struct for a rule, and just use a dictionary for fast look up. As for the limit just add it as an argument to the function. You can also put the second for loop in its own function, and give it a readable name.
@randyclark7433
@randyclark7433 9 месяцев назад
Also should probably let another function do the logging.
@BenceSchneider
@BenceSchneider 9 месяцев назад
Why not set the initial value of the output to 'i', so the empty check can be avoided in the end?
@WilliamJohnson-xs2vi
@WilliamJohnson-xs2vi 9 месяцев назад
Because we want the strings to replace the number when we hit one of the conditions. The function appends the string to the value, so if the output started out equal to i, and i = 3, we would get a result of "3Fizz".
@BenceSchneider
@BenceSchneider 9 месяцев назад
@@WilliamJohnson-xs2vi ah, thanks, i missed that, you're right!
@YourComputer
@YourComputer 9 месяцев назад
Doesn't this force the rule set to be in a specific order? So, as far as having "clearly understandable rules" and "a very simple way to adapt the code," might not be entirely true.
@jamesfoo8999
@jamesfoo8999 9 месяцев назад
yeah the functionality is tightly coupled to the ruleset and in a specific order. Not good really.
@OzixiThrill
@OzixiThrill 8 месяцев назад
That really depends on the way any future alterations might deviate from the established pattern. If there are no "irrregular" patterns, then this 100% covers it. If irregular patterns are uniform (%7 and %11 ALWAYS are written in reverse order) then this solution still 100% covers that (you only have to change the order they are represented with in the ruleset). Anything past that, you'd have to separately handle the exceptional cases (for example, %7 and %11 are reversed only if they are the only two rules that apply, that's something you'd have to test after the initial output test is done.
@mac.ignacio
@mac.ignacio 8 месяцев назад
Suggestion 1. Pass the limit as an argument to your function. 2. Replace the array with object. Key as the number and value is the message. 3. Replace the 2nd for loop with "for in"
@bit-machine
@bit-machine 8 месяцев назад
Good suggestions. I like the way you think!
@coinbongo4694
@coinbongo4694 8 месяцев назад
I belive having the input parameters be a in for the loop limit and a array of tuples of type int and string would make this more easily undersatandable for future development.
@steveftoth
@steveftoth 9 месяцев назад
If you do this in an interview I’m not hiring someone who wants to implement something complex when something simple will also work. Just saying the solution complexity should match the problems complexity. Don’t need to drive a car down the block when you can walk.
@wlockuz4467
@wlockuz4467 9 месяцев назад
That's the thing about programming. You can do an endless amount of mumbo jumbo to make your code nicer, faster, elegant or whatever. But you need to understand what are the actual requirements for the code that you're writing. I personally would stop at something like Tom's code and ask the interviewer for the feedback. If they ask me to go further, only then I would do a refactor like this.
@oryged9990
@oryged9990 9 месяцев назад
my first instinct as a python beginner would be to use a boolean-mask which takes even less code. Would something like that be valid?
@jamesfoo8999
@jamesfoo8999 9 месяцев назад
Why don't you try it. Write it and see if it's viable :)
@oryged9990
@oryged9990 9 месяцев назад
@@jamesfoo8999 well yeah it works but im not sure if its super efficient on a large scale. (It takes less time on timeit though)
@jamesfoo8999
@jamesfoo8999 9 месяцев назад
@@oryged9990 so now benchmark it :) honestly this is a good way to learn, by playing around outside commercial code. You can do what you want without fear of causing problems. When you have a benchmark setup, you can just change the code and rerun the tester and see if another way is more performant. Rinse and repeat
@15jorada
@15jorada 8 месяцев назад
This is what I did in python "Fizz"*(i%3==0)+"Buzz"*(i%5==0)+str(i)*(i%3!=0)*(i%5!=0) I think it is O(n) but I'm not a computer science guy so take that as you will
@mickys8065
@mickys8065 8 месяцев назад
The way I'd personally consider doing it is getting the final version of Tom's code, and throwing the if statements into its own function, like this; Function isMod(_request, _reply, _answer){ If _request == 0, then _answer += _reply } Then in the main function, you'd do Loop { isMod(3 % i, "Fizz", answer) isMod(5 % i, "Buzz", answer) ... } To me, it's easier to decode and change, although I'm not too sure about the best way to do the += answer yet. But, ultimately you go to the loop to 100, and it's clear "this one is sending 3 % 0, and the string Fizz. What is isMod doing? Oh it's doing X. Ah, now this next line is 5 % 0, using the same function." If the rules for the game itself change, it's either in the function or the parent. And as a benefit, it doesn't use global variables.
@MrAbrazildo
@MrAbrazildo 8 месяцев назад
I often don't use generic programming (what you are doing), because I want to "encapsulate a behavior" inside the f(). Once you open it to external content, it becomes harder to debug, because now you'll have to look to calls to that f() too. I'm not saying that 1 way is better than other, since both have pros and cons. What I use to do is begin coupled, only freeing stuff when the practice pushes me to. But this is for a project. If it's meant for reusable code, the contrary if often recommended: start by generic stuff, only coupling it if some strong reason appears. For instance: performance (less thing to deal with), safety (less chance for user make a mistake), and so on.
@karolstopinski8350
@karolstopinski8350 8 месяцев назад
Note that when reusing the function in a different project there should be some kind of note inside the function on to what data this thing is taking. In such a trivial example it`s fairly simple but if you have something more complicated then it`s difficult from inside the function deduct what is supposed to be passed in. So when someone is reusing the function they will need to grab the ruleset that goes with it as an example.
@kc5402
@kc5402 9 месяцев назад
I guessed the right answer before the video reached the final section! Three cheers: "Hip-hip! Array!" "Hip-hip! Array!" "Hip-hip! Array!" 😉😁
@jfftck
@jfftck 8 месяцев назад
What if the rule set starts having other requirements, like how leap year is every 4 years except for the millennium. This wouldn’t allow for that type of change and this change is still tightly coupled, instead it would be more abstract and clearer to create a closure function with a signature of multipleOf(num, word) that then returns a function with this signature (x) => x % num === 0 ? word : ‘’ and the loop would just have to concatenate the return value with the existing one. The big advantage is the ability to create complex rules to add to the set.
@gingeral253
@gingeral253 9 месяцев назад
What language is this? I don’t really understand the code syntax
@disrate
@disrate 9 месяцев назад
java
@gingeral253
@gingeral253 9 месяцев назад
@@disrate Clearly not. I use Java and we don’t have let in our code. It could be JavaScript since I’m not as familiar with its syntax.
@disrate
@disrate 9 месяцев назад
@@gingeral253 that’s what I meant
@kuba4ful
@kuba4ful 8 месяцев назад
@@disrate why did you call it java then lol. those two are completely different beasts despite having very similar name.
@disrate
@disrate 8 месяцев назад
@@kuba4ful they’re same thing tbh
@FireSiku
@FireSiku 8 месяцев назад
Based on the comments, I was really waiting for the FizzBuzzEnterprise approach of solving this problem. Who doesn't like to have 23 interfaces and 59 classes to do something that can be done in 15 lines of code?!
@o_2731
@o_2731 9 месяцев назад
Very interesting deign pattern :D
@sitter2207
@sitter2207 9 месяцев назад
What this video shows i why all my code in my first 3 years of learning programming was so terrible and unreadable. I always tried to make my code more generic, shorter, dry AF. I used it as a hard-set rule. Strive to make performant (if it matters) and readable code. Not "future-proof", "abstracted" code. It is a much simpler and obvious rule, it is a better candidate for a hard-set rule because 'readable' depends on the situation. Readable, performant. Repeat yourself a hundred times if that's what it takes for this. Then comes things like testable code and seperation of concerns. And it always depends. Strive to write good code, which is none of the code presented here, because good code depends on the situation.
@maticz3923
@maticz3923 9 месяцев назад
This example is too simple to show how it saves on complexity. In a complex codebase what here is just one if can be many ifs scattered across different places. Now every time you want to change this and let's say do the equivalent of adding a new word and divisor here, you have to look for all the ifs in your code, add your new ones, make sure you don't make a mistake just copying the other ifs and make sure you don't skip a single place where it exists DRY isn't always what you want to use, but there is no point writing verbose code with all the "boilerplate" of if(x % ... == 0) output += ...
@CallousCoder
@CallousCoder 8 месяцев назад
@@maticz3923I guess that is what the author meant. Good code depends on the situation. This very silky simple situation would never justify this level of abstraction. But if it for example needed to be configurable from a user user, then it is. The thing is to always balance its always easy to over engineer but engineering is to strike the right balance.
@bened22
@bened22 8 месяцев назад
You have succeeded in the practice all programmers sooner or later become masters in: Taking a simple solution everybody can understand and making an overly complex and mind-consuming mess out of it. I love it!
@DiegoLuiz
@DiegoLuiz 8 месяцев назад
Apart from having the limit in the same object as the rules, it's neater.. but it's borderline enterprise solution
@illya_ike
@illya_ike 8 месяцев назад
It is not always a good idea to implement generic solutions for every small problem. Generic solutions are usually more complex - you think that this extra complexity will help in the future, but the thing is that you never know what you will need in the future - you may never touch this code again. And if you will always implement only generic solutions for every small task - this will significantly slow down your progress. So yes - if you have all the time in the world - why don't make everything perfect. But in reality I think a better approach is to get things done quickly using reasonably good code (i.e. avoiding well recognized software design antipatterns) - and only implement generic solutions when you see that certain part of your code actually needs it - i.e. you see that you need to update it over and over again.
@Sammysapphira
@Sammysapphira 8 месяцев назад
Writing technical debt causes more slowdown.
@illya_ike
@illya_ike 8 месяцев назад
@@Sammysapphira not having generic and therefore overcomplicated code isn't a technical dept. Simple solution that is designed to solve exact task is even easier to maintain - KISS principle.
@xxxPrzybyLxxx
@xxxPrzybyLxxx 9 месяцев назад
Yes, definitely - let's plan LONG for the future a simple function!
@jamesfoo8999
@jamesfoo8999 9 месяцев назад
I know what you mean, but clean code is clean code, whether 10 lines and simple or 100 lines and complex. It makes it easier to understand (no matter how small/simple) and easier to extend and alter in the future.
@godDIEmanLIVE
@godDIEmanLIVE 9 месяцев назад
@@jamesfoo8999 clean code is a fraud.
@CallousCoder
@CallousCoder 8 месяцев назад
@@jamesfoo8999clean code is slow code, both to write and to run. Over abstraction is a sin just like early optimization.
@jamesfoo8999
@jamesfoo8999 8 месяцев назад
@@CallousCoder clean code is clean code. It's slow to initially write, but fast (easy) to maintain, read, understand, document, extend... enjoy your legacy :)
@CallousCoder
@CallousCoder 8 месяцев назад
@@jamesfoo8999 clean code is a religion because it’s not objectively measurable. And is it better maintainable that also is subjective and depends on the quality of your developers. It’s a fact that code these days has an a life span of only 4 years! That’s insanely short it’s even shorter than the fiscal depreciation so things are obviously not going well. Code in the past (and much of it still) ran for 10-15 years. Most code in the backend of energy, healthcare, industrial complex is decades old and still does work perfectly - and is fast especially when brought to new hardware. Unlike all the modern software, that performs significantly slower. Mainly because of stupidity of using web development and interpreted languages and microservices that are all memory hungry and introduce unnecessary dependencies en complexity. So you may say that your new software is the new legacy because most of it is probably already gone after 3 years. And again to stress this, when you have a problem maintaining someone else’s code it is a skill issue. Sure we all think differently we all solve problems differently and it may take a little side step. But then a good engineer can reverse engineer it. I do this with binaries even, have some videos on this channel where I hack videos games to achieve certain things. And I don’t even have access to the code base just the assembly. The PacMan video to get to the kill screen was a particularly tricky one because there was even a checksum implemented and obfuscated so that people couldn’t hack the ROMs. Even that I managed to do and it’s written in Z80 assembly not even the assembly in very familiar with.
@felixjohnson3874
@felixjohnson3874 8 месяцев назад
Agree with the general abstraction but disagree on implementation. I'd just add the limit as a parameter to the function and then either use two index-related arrays for the rules (remember to add checks!) or an associative hashmap with some way of iterating through each key. Then it's as easy as Fizzbuzz(100, {5:"fizz",7:"buzz"}) keeping the code simple AND making it's inputs simple & easy to understand. (Plus being performant)
@aknkrstozkn
@aknkrstozkn 9 месяцев назад
I don't think using strings, a specially C# strings make it approach better. I could use some repetitive if sequence rather than filling and clearing a string every loop.
@mr.rabbit5642
@mr.rabbit5642 9 месяцев назад
My fav change would be to set output to 'i' inside the console log itself, as " console.log(output|| i) ", shortening it further and removing an awfull 'if' clause. [And since its using an 'or' operator I do not consider it an "insider-only knowledge" and would expect basically anyone to understand it, without 5 years of JS experience..]
@bit-machine
@bit-machine 9 месяцев назад
That's a really good point. I'll make sure to include that in the revised video and credit you with the idea. Thanks!
@lupuionut6286
@lupuionut6286 8 месяцев назад
If it works, it works
@the_allucinator
@the_allucinator 8 месяцев назад
You could just set the limit as a second argument rather than declaring it the first element of the array.
@bit-machine
@bit-machine 8 месяцев назад
I realised that after I made the follow up video to this. Now it's too late to change it. But you're right, that also makes the limit variable easy to set to a custom value when the function is called. Would have been much better.
@furetosan
@furetosan 9 месяцев назад
Cheap YT shot, but still a good one
@marilatte539
@marilatte539 8 месяцев назад
As a conrast to other commenters I like your idea, but I would change the ruleset. As you said it can be its own object but then I would rather declare an actual object that has a limit property and an array property instead of a "hacky" array that is very unclear. I also think that in this example using function arguments would be enough (again limit, words). Of course, when the function gets more complicated you might want to use more complex objects, here it really depends what is easier to read. Having to look up the structure of the ruleset (no matter if its an object or function arguments) is not really an error you made but instead comes from the fact that JavaScript works like that. As others have said you can use documentation or typescript.
@captheobbyist6434
@captheobbyist6434 8 месяцев назад
i would personally put the limit into a different variable and leave the ruleset with how it is
@tylerbakeman
@tylerbakeman 8 месяцев назад
You didn’t improve the code. It is the same code (technically very slightly slower). It’s nice that you recognize how you can generalize his code into a loop, and customize the significant numbers with a collection; however: for( i; i < 100; i++) VS for( i; i < limit; i++) // limit should be const btw The first for loop is explicite, so it is easier to read and works. There isn’t a need to make a limit variable if you don’t allow variability… limit doesn’t change unless you manually type it in - which is the same as updating the loop directly. Same logic for the collection. Using a map, you can get create a map of FizzBuzz related sounds: Map< Integer, String > fizzes = new HashMap(); … Add “Fizz”, “Buzz”, “… , to fizzes … for( int i = 0; i < limit; i++ ) { int fizz = fizzes.get(i); print( (fizz != null)? fizz : i+””; ); } // (String)i is probably fine too - I’m just used to adding strings to variables Anyhow- using a map would be more common for your syntax, for bigger projects of longer collections: it’s scalable. If your collection is really short and fixed-length, probably don’t bother with the map.
@imqqmi
@imqqmi 8 месяцев назад
I miss the process of defining requirements the bit of functionality is supposed to have. You can willy nilly make the code more generic, abstract etc but was it asked for? This often causes real world projects to go over budget. Yes you can put the data in a db, slap on a gui for editing, logging, monitoring, scheduled batch processing, do a spot of separation of concern, setup some devops configuration variables etc etc. They are only improvements when called for, otherwise it's just bloat. Takes you a month to do knowledge transfer too adding to the mess you leave behind.
@freakpandor
@freakpandor 7 месяцев назад
num_list = [i for i in range(1, 101)] result = map(lambda a: "FissBuzz" if a % 3 == 0 and a % 5 == 0 else ( "Fiss" if a % 3 == 0 else ("Buzz" if a % 5 == 0 else a) ), num_list ) for j in result: print(j)
@hansvanzutphen
@hansvanzutphen 8 месяцев назад
Bad advise. The new code is more difficult to understand, and while it's easy to change or add numbers, what if instead of number % 3 you want to check for (number + 1) % 3? In Tom's original code that's easy, in yours it's not possible without adding extra arguments to the ruleset, which will easily lead to incomprehensible code. I would agree with making 100 a parameter of the function, but the rest is just needlessly complex.
@josedallasta
@josedallasta 8 месяцев назад
better version of your solution: instead of running the for loop inside the function, passing the array to it, you should run the function inside a for loop and remove the loop from that function this contributes to the SOLID standard and will help keeping things clean. if the function is to print if said number is true or false, then it should be this and only this we should not have to initialize an array just to check one number think functions as services and whatever is calling them as logic, the for loop should stay in the logic layer, not in the service one
@TurquoiseIcy
@TurquoiseIcy 8 месяцев назад
Wouldn't it be better to divide the ruleset into different parameters? Mixing the ints and the strs together is NOT good.
@jasonknight1085
@jasonknight1085 8 месяцев назад
You know, 35 years ago the nun who was my first real mentor would have smacked you across the head with a wooden yardstick for coding like that. Congratulations! You put a loop inside the loop, added set lookups, created zero extra code clarity,, introduced spaghetti by having functionality outside the flow, and pissed on performance from so on-high you'd think the almighty just got back from a kegger. MORE steps, more comparisons, more complexity, more fragility... And then have the nerve to call it an improvement. This is why most people talking about "clean code" are doing little more than flapping their arse cheeks in the wind. And why? "wah wah teh if-else es teh evuls" and damned little other REASONABLE objections.
@NuncNuncNuncNunc
@NuncNuncNuncNunc 8 месяцев назад
Making it worse by making it better 1) limit is coupled to rules 2) rule order is not specified in code but is in spec - swapping rule order breaks the solution 3) inner for loop followed by the if statement obscures what is actually being done - this should be function that takes a number and returns the appropriate string 4) the rules are testable only by running over the entire range. Solution should be function that that takes range and the rule function. This is really just mapping over a range so [start..end].forEach(rules), for example where [1..end] is the inclusive range and forEach is whatever method your language uses to iterate over a list.
@Tobiandertaler
@Tobiandertaler 9 месяцев назад
Personally I wouldn't combine the limitation and the word rules into one ruleset object. Instead you could keep the ruleset having just the word rules and move the limitation part into your fizzBuzz function, maybe using declarative programming paradigm too like this: ruleset = [ { number: 3, word: "Fizz" }, { number: 5, word: "Buzz" } ]; function fizzBuzz(ruleset, limit) { for (let i = 1; i i % rule.number == 0) .map(rule => rule.word); console.log(words.length ? words.join("") : i); } }
@explodatedfaces
@explodatedfaces 9 месяцев назад
The problem with using abstraction to solve every problem is exactly what Tom Scott mentioned in his video already; it doesn't work as well with very large projects. This is exactly how you end up with 55,000 classes or 75,000 functions that are so woven together in a complex web of spaghetti noodles that anyone who has to work with your solution in the future has to first start by rewriting the entire thing over the course of 10 years and at the expense of tens of millions of dollars. Abstracting everything to prevent yourself from repeating the same words on a page solves no problem except needing to make more pages.
@DNA912
@DNA912 9 месяцев назад
adding more classes and function will only add unnecessary complexly if you don't know why you're decomposing it. I'm currently working in a 20 year old codebase and decomposing is exactly what they didn't do 10 years ago. so when we need to change a behaviour, we some times need to change code in 10 places because no one had the energy to create a function that did that thing and call that function 10 times. (I'm talking same logic in different classes and even servers). But I agree with you that abstraction and decomposition for decompositions sake is useless and can make the code completely unreadable as well.
@maticz3923
@maticz3923 9 месяцев назад
@@DNA912 absolutely right
@explodatedfaces
@explodatedfaces 9 месяцев назад
@DNA912 i mean thats great and all but again... if it takes you 10 years to put out a new rules based decision engine (as an example) because you're afraid of saying the words "if x =" more than once in a class you might just be insane. Repeat yourself. Its easier to read than a novel of classes and your program will need less resources to function.
@DNA912
@DNA912 9 месяцев назад
@@explodatedfaces agree, repetition in one class isn't a problem, and readability is great, it's when you (in the long run) create the same logic in 5 different classes which is the problem, because when you updated the code you forgot there actually was 6 classes with the same logic and not 5.
@daisugabatabata
@daisugabatabata 9 месяцев назад
JIT is gonna hate that function
@user-bu5qn7oe4w
@user-bu5qn7oe4w 9 месяцев назад
Use object destructuring
@farghostable
@farghostable 9 месяцев назад
This is not really a good example. in fact this is clearly a bad example. 1. Separation of the ruleset itself makes the problem harder in most cases, except exactly the case where you need to expand the ruleset. (You need to check 2 places if any of this problem changed.) 2. The provided solution is unnecessary expanded into the future, which is not needed right now. And it is less performant, and less readable because use of extra concept of "ruleset" 3. Probability that the requirements for the software will expand into direction you "imagined" is low. So, - 1. wasted performance. 2. unnecessary abstraction 3.expanded in the certain direction, without actually knowing where this piece of software headed. It is a bad solution, by this terms. I've done this mistake hundreds times. I expect that after this code pushed in production, next thing is Manager is walking to talk to you and say "we no longer need to print fizzbuzz, we need to request the user input if i divisible by 3, and to run an account check if the number is represented as XVIII in romans", and the whole thing goes out the window.
@CallousCoder
@CallousCoder 8 месяцев назад
I agree this example isn’t great. The first thing I always ask when I get such a requirement is: “how likely is it going to change?” That single question can save hours of useless abstraction and configuration parsing code. Because every abstraction means extra risk off errors, more testing required more complexity to overlook. I love micro optimization but I also don’t start with optimization unless it is deemed slow and in that case the first thing you again will sacrifice is abstraction when not needed 😂
@jameswagstaff1962
@jameswagstaff1962 8 месяцев назад
This would not work for object oriented languages. I also wouldn't use nested loops
@snoozyboio
@snoozyboio 9 месяцев назад
I'm glad that your code ACTUALLY solves the issue of writing "fizzbuzz" if the numebr is divisible by 3 and 5, unlike tom's video
@andygrant3404
@andygrant3404 3 месяца назад
ChatGPT is that you?
@bit-machine
@bit-machine 3 месяца назад
I'm his little brother, won'tstoptalkingGPT but it's okay, I hear that all the time.
@D3f4uLT_
@D3f4uLT_ 9 месяцев назад
1:20 "This is probably what most non-professional people would right." : *Really* !? 💀😂
@sharperguy
@sharperguy 2 месяца назад
It works fine until you find out that the first change you need to make is something completely unanticipated, like reading from a database to get the users birthday, and writing "happy birthday" if the number matches. Then not only do you have to refactor the code but also the additional complexity you added in the configurable ruleset, which didn't end up being useful.
@BrotWurst
@BrotWurst 9 месяцев назад
4:15 i dont think the really "best" idea ist to have a array of unknown objects and pointing to the first entry of the array and the objects content/key "limit" like this. this code breaks if you swap the position or push something to the first position of the array. we should rely on key pointers on lists. this is not the purpose of a list. yes, a list can contain objects. and nnnyes these objects may differ. but accessing a fixed value on the FIRST entry of an array and accessing the single stored variable just screames for future problems and runtime errors. especially because it semms like you are using JS instead of TS in this scenarios and dont use optional chaining
@BrotWurst
@BrotWurst 9 месяцев назад
if you would use Typescript and define an interface for it, i could live better with that. because in compile time while you code it will scream at you that the rulesets first entry should always be an object containing the "limit" key with a number value. also is the "limit" really a "rule"? if so, maybe we should/could rethink the rules itself. ruleSet: { limit: number, rules: [ { word: String, number: Number, }, ], }
@BrotWurst
@BrotWurst 9 месяцев назад
with this code you can be sure that the rules are all the same key-value pairs. and also the limit has to be set and is not itself a rule. it isnt really a "rule" in the loop itself, its defined and used just outside of the loop.
@-rya1146
@-rya1146 9 месяцев назад
Thats not the point. The point is to use a structured object to encapsulate the required data which can be used uniformly. Granted it is all about 'what you need' vs 'what is good practice' and striking a balance of both is a different issue. Typically, going all gungho on 'best practices' causes the core problem to be overthinked sortof like polishing a turd.
@HansBezemer
@HansBezemer 7 месяцев назад
It's a bad idea to incorporate the count in the ruleset. It's not the same kind of data - and if we want another count, we have to copy the entire shebang. Better make it a parameter for the function. If you *really* want to abstract it, you could make the operator and the execution into function pointers.
@apocalyptosoldier5527
@apocalyptosoldier5527 8 месяцев назад
I don't think hardcoded indices are a great idea, I'd rather the limit be a separate parameter, or have an object with the properties limit and ruleset. And if you're already going to complicate things to the point of having nested loops then you might as well go the extra step and move the inner loop to a separate function.
@1337pianoman
@1337pianoman 8 месяцев назад
Obviously this is a toy problem, but I think even the methodology is flawed. Your assumptions are that: 1. the rules are going to change in the future 2. The person updating the rules won't need to need to look at the actual code anyway 3. The hypothetical future rules change is compatible with the current system for defining rules In my experience, none of these assumptions is ever correct. There are two ways this plays out. Most of the time the code is never touched again and any work you put into making it flexible is wasted. In the situations where the code does need to be updated, the next person (which is probably just you again but having forgotten what you did) is going to have to look at the guts of the function. And the change will end up being just different enough from what you predicted it could be that you have to rewrite it anyway. It is practically always better to write the simple version first and only make it reusable when you actually have to reuse it. By turning everything into a modular composible framework you are adding unnecessary complexity which ultimately makes the code less maintainable
@rexroyulada6267
@rexroyulada6267 6 месяцев назад
I'm using python and my solution is this: x = input() for i in range(x): k = "" alpha = 3 beta = 5 c_alpha = (i % alpha == 0) c_beta = (i % beta == 0) if c_alpha and c_beta: k = "Fizzbuzz" elif c_alpha: k = "Fizz" elif c_beta: k = "Buzz" else: k = i print(k)
@rexroyulada6267
@rexroyulada6267 6 месяцев назад
Sorry, I haven't finished the video and just saw the Fizzbuzz challenge
@rexroyulada6267
@rexroyulada6267 6 месяцев назад
Here's the new code after I was done watching this: ruleset = [ {"limit": 100}, {"number": 3, "word": "Fizz"}, {"number": 5, "word": "Buzz"}] def FizzBuzz(ruleset): for i in range(ruleset[0]["limit"]): output = "" for l in range(1, len(ruleset)): if (i % (ruleset[l]["number"]) == 0): output += ruleset[l]["word"] if output == "": output = i print(output) FizzBuzz(ruleset)
@zoltantorok1189
@zoltantorok1189 9 месяцев назад
Please do more "improving someone else's code" videos.
@wingwong1071
@wingwong1071 9 месяцев назад
Sorry but I do not think the ruleset should be an array of map.
@mypotatoesarenice
@mypotatoesarenice 8 месяцев назад
Good to know for new devs, bad practice in reality. Do just as NECESSARY, not more. It will bite you in the ass in the future
@stintaa
@stintaa 9 месяцев назад
“improved”: now the developer that would be maintaining this code would have to jump around, decreasing DX.
@CallousCoder
@CallousCoder 8 месяцев назад
It’s true that this is “somewhat” easier to extend and more importantly since you extend a structure less error prone than extending logic. But…. This is slower especially in most interpreted languages. Because that rules set loop, adds extra branching for each rule and each thus iteration munching up extra cycles. In 1-100 yiu don’t notice it but doing it million or billion times those cycles do start to add up. When you have if statements or better yet a switch/case block, the code will be unrolled and reduce that inner rule set loop. A good compiler with optimizing set for speed, will most likely just unroll that rule set too. So the main take away is, use a compiled language that has great optimization, so you can write with abstractions and incur little to no extra cost. 😊
@BlazingMagpie
@BlazingMagpie 8 месяцев назад
I cannot take an argument to use compiled languages because they can run FizzBuzz faster seriously.
@CallousCoder
@CallousCoder 8 месяцев назад
@@BlazingMagpie it’s not about fizzbuzz its about programming iterative processes in general. A good compiler will always kill that interpreter because it can optimize for speed in the most ideal way. And you can do more abstraction with far less of a cost than with an integrated language - as I have demonstrated in my argument how unrolled optimized code would basically reduce the abstraction overhead to a minimal overhead compared to an interpret language. Also try fizz buzz over 1 billion iterations in Python and in day C. I did a video on Zig programming and i toon er SMaC problem, very similar really. It’s not the problem itself but it’s a vehicle to show 3 versions a single threaded version, a multi threaded without exclusive locks and a network version that streams the problem over a socket. It’s a great vehicle to show different methodologies like all academic examples, because the problem is trivial so you can focus on other aspects more easily like parallelism, unrolled vs branching etc.
Далее
Is My FizzBuzz Solution Terrible?
5:57
Просмотров 42 тыс.
Разоблачение ушные свечи
00:28
Просмотров 641 тыс.
Мой инстаграм: v1.ann
00:13
Просмотров 74 тыс.
Harder Than It Seems? 5 Minute Timer in C++
20:10
Просмотров 163 тыс.
FizzBuzz - You Suck at Coding [0]
12:35
Просмотров 404 тыс.
How principled coders outperform the competition
11:11
Why You Shouldn't Nest Your Code
8:30
Просмотров 2,6 млн
The Worst Typo I Ever Made
11:25
Просмотров 6 млн
25 nooby Python habits you need to ditch
9:12
Просмотров 1,7 млн
The 3 Laws of Writing Readable Code
5:28
Просмотров 447 тыс.
The purest coding style, where bugs are near impossible
10:25
How Senior Programmers ACTUALLY Write Code
13:37
Просмотров 1,5 млн
Разоблачение ушные свечи
00:28
Просмотров 641 тыс.