[HN Gopher] It's probably time to stop recommending Clean Code (... ___________________________________________________________________ It's probably time to stop recommending Clean Code (2020) Author : avinassh Score : 486 points Date : 2021-05-25 13:48 UTC (9 hours ago) (HTM) web link (qntm.org) (TXT) w3m dump (qntm.org) | pixel_tracing wrote: | I worked at a large billion dollar company in the Bay Area (who | is in the health space) and they religiously followed Clean Code. | Their main architect was such a zealot for it. My problem is not | with the book and author itself but the seniority that peddles | this as some gospel to the more junior engineers. Clean code is | not end all be all. Be your own person and develop WHAT IS RIGHT | FOR YOUR ORG not peddle some book as gospel | | So glad I work at a company now where we actually THINK on the | right abstractions now and not peddle some book | [deleted] | grouphugs wrote: | it's probably time to stop recommending alt-right and fascist | products, but you're all literally nazis, and you're going to die | like ones | phibz wrote: | This is the first time I've heard of this book. I certainly agree | some of these recommendations are way off the mark. | | One guideline I've always tried to keep in mind is that | statistically speaking, the number of bugs in a function goes way | up when the code exceeds a page or so in length. I try to keep | that in mind. I still routinely write functions well over a page | in length but I give them extra care when they do, lots of | comments and I make sure there's a "narrative flow" to the logic. | sumtechguy wrote: | The big one to keep an eye on is cyclomatic complexity with | respect to function length. Just 3 conditional statements in | your code gives you no less than 8 ways through your code and | it only goes up from there. | | All of these 'clean code' style systems have the same flaw. | People follow them without understanding why the system was | made. It is why you see companies put in ping pong tables, but | no one uses them. They saw what someone else was doing and they | were successful so they copy them. Not understanding why the | ping pong table was there. They ignore the reason the | chesterton's fence was built. Which is just as important if you | are removing it. Clean code by itself is 'ok'. I personally am | not very good at that particular style of coding. I do like | that it makes things very nice to decompose into testing units. | | A downside to this style of coding is it can hide complexity | with an even more complex framework. It seems to have a nasty | side effect of smearing the code across dozens of | functions/methods which is harder in some ways to get the 'big | picture'. You can wander into a meeting and say 'my method has | CC of 1' but the realty is that thing is called at the bottom | of a for loop, inside of 2 other if conditions. But you 'pass' | because your function is short. | tziki wrote: | Number of bugs per line also goes way up when the average | length of functions goes below 5, and the effect in most | studies is larger than the effect of too large functions. | rantwasp wrote: | 4 line functions everywhere is insanity. yes, you should aim | for short functions that do one thing, but in the real world | readability and maintainability would suffer greatly if you | fragment everything down to an arbitrarily small number. | auraham wrote: | I am interested in reading books about software development and | best practices like Clean Code and The Pragmatic Programmer [0]. | I have coded for about eight years, but I would like to do it | better. I would like to know your opinion about [0], since Clean | Code has been significantly criticized. | | [0] https://pragprog.com/titles/tpp20/the-pragmatic- | programmer-2... | yoz-y wrote: | Articles like these make me feel better about never having read | any of the 'how to code' books. Mainly substituting them by | reading TheDailyWTF during the formative years. | senderista wrote: | What well-regarded codebases has this author written, so you can | see his principles in action? OTOH, if you're wondering about the | quality of John Ousterhout's advice in _A Philosophy of Software | Design_, you can just read the Tcl source. | Communitivity wrote: | For me Clean Code is not about slavishly adhering to the rules | therein, but about guidelines to help make your code better if | you follow them, in most circumstances. On his blog Bob Martin | himself says about livable code vs pristinely clean code: "Does | this rule apply to code? It absolutely does! When I write code I | fight very hard to keep it clean. But there are also little | places where I break the rules specifically because those | breakages create affordances for transient issues." | | I've found the Clean Code guidelines very useful. Your team's | mileage may very. As always: Use what works, toss the rest, give | back where you can. | | For more about this see: | | * Bob Martin's blog post 'Too Clean' - | https://blog.cleancoder.com/uncle-bob/2018/08/13/TooClean.ht... | | * Livable Code by Sarah Mei - | https://www.youtube.com/watch?v=8_UoDmJi7U8 | bena wrote: | The problem with Clean Code is also the problem with saying to | ignore Clean Code. If you treat everything as a dogmatic rule on | how to do things, you're going to have a bad time. | | Because, they're more like guidelines. If you try not to repeat | yourself, you'll generally wind up with better code. If you try | to make your methods short, you'll generally wind up with better | code. | | However, if you abuse partial just to meet some arbitrary length | requirement, then you haven't really understood the reason for | the guideline. | darksaints wrote: | But the problem isn't so much because the book has a mix of | good and bad recommendations. We as an evolutionary race have | been pretty good at selectively filtering out bad | recommendations over the long term. | | The problem is that Uncle Bob has a delusional cult following | (that he deliberately cultivated), which takes everything he | says at face value, and are willing to drown out any dissenting | voices with a non-stop barrage of bullshit platitudes. | | There are plenty of ideas in Clean Code that are great, and | there are plenty that are terrible...but the religiosity of | adherence to it prevents of from separating the two. | rglover wrote: | FWIW the clean code approach led me to this pattern which has | allowed me to build some seriously complex systems in JS/Node: | https://ponyfoo.com/articles/action-pattern-clean-obvious-te.... | | I agree with the sentiment that you don't want to over abstract, | but Bob doesn't suggest that (as far as I know). He suggests | _extract till you drop_ , meaning simplify your functions down to | doing one thing and one thing only and then compose them | together. | | Hands down, one of the best bits I learned from Bob was the "your | code should read like well-written prose." That has enabled me to | write some seriously easy to maintain code. | nichch wrote: | Off topic but is connecting to Mongo on every API hit best | practice? I abstract my connection to a module and keep that | open for the life of the application. | winrid wrote: | Nice! However, none of this is required for this endpoint. | Here's why: | | 1. The connect action could be replaced by doing the connection | once on app startup. | | 2. The validation could be replaced with middleware like | express-joi. | | 3. The stripe/email steps should be asynchronous (ex: simple | crons). This way, you create the user and _that 's it_. If | Stripe is down, or the email provider is down, you still create | the user. If the server restarts while someone calls the | endpoint, you don't end up with a user with invalid Stripe | config. You just create a user with stripeSetup=false and | welcomeEmailSent=false and have some crons that every 5 seconds | query for these users and do their work. Also, ensure you query | for false and not "not equal to true" here as it's not | efficient. | MaxBarraclough wrote: | > your code should read like well-written prose | | That strikes me as being too vague to be of practical use. I | suspect the worst programmers can convince themselves their | code is akin to poetry, as bad programmers are almost by | definition unable to tell the difference. (Thinking back to | when I was learning programming, I'm sure that was true of me.) | To be valuable, advice needs to be specific. | | If you see a pattern of a junior developer committing | unacceptably poor quality code, I doubt it would be productive | to tell them _Try to make it read more like prose._ Instead you | 'd give more concrete advice, such as choosing good variable | names, or the SOLID principles, or judicious use of comments, | or sensible indentation. | | Perhaps I'm missing something though. In what way was the _code | should read like well-written prose_ advice helpful to you? | flylikeabanana wrote: | I'm in the "code should read like poetry" camp. Poetry is the | act of conveying meaning that isn't completely semantic - | meter and rhyme being the primary examples. In code, that can | mean maintaining a cadence of variable names, use of | whitespace that helps illuminate structure, or writing blocks | or classes where the appearance of the code itself has some | mapping to what it does. You can kludge a solution together, | or craft a context in which the suchness of what you are | trying to convey becomes clear in a narrative climax. | dsego wrote: | > use of whitespace that helps illuminate structure, | | Good luck with that now that everybody uses automatic | linters & formaters that mess up your whitespace because of | some stupid rule that there should be only one empty line | and no spaces after a function name or something. | rglover wrote: | Specifically in relation to naming. I was caught up in the | dogma of "things need to be short" (e.g., using silly | variable names like getConf instead of getWebpackConfig). The | difference is subtle, but that combined with reading my code | aloud to see if it reads like a sentence ("prose") is | helpful. | | For example, using what I learned I read this code | (https://github.com/cheatcode/nodejs-server- | boilerplate/blob/...) as: | | "This module is going to generate a password reset token. | First, it's going to make sure we have an emailAddress as an | input, then it's going to generate a random string which I'll | refer to as token, and then I want to set that token on the | user with this email address." | MaxBarraclough wrote: | So you're interpreting it to mean _use identifiers which | are as descriptive as they can practically be, and which | are meaningful and self-explanatory when used in | combination_. | | I agree that's generally good advice; the mathematical | style of using extremely short identifiers generally just | confuses matters. (Exception: very short-lived variables | whose purpose is immediately clear from the context.) It's | only one possible interpretation of _code should read like | prose_ , though. It you who deserves the credit there, not | Bob. | | > silly variable names like getConf instead of | getWebpackConfig | | To nitpick this particular example: I'd say that, if it's a | method of an object which is itself specific to WebPack, | then the shorter identifier is fine. | lamontcg wrote: | I like to think of the way Hemmingway writes. | | Code should be simple and tight and small. It should also, | however, strive for an eighth grade reading level. | | You shouldn't try to make your classes so small that you're | abusing something like nested ternary operators which are | difficult to read. You shouldn't try to break up your | concepts so much that while the sentences are easy, the | meaning of the whole class becomes muddled. You should stick | with concepts everyone knows and not try to invent your own | domain specific language in every class. | | Less code is always more, right up until it becomes difficult | to read, then you've gone too far. On the other hand if you | extract a helper method from a method which read fine to | begin with, then you've made the code harder to read, not | easier, because its now bigger with an extra concept. But if | that was a horrible conditional with four clauses which you | can express with a "NeedsFrobbing" method and a comment about | it, then carry on (generating four methods from that | conditional to "simplify" it is usually worse, though, due to | the introduction of four concepts that could be often better | addressed with just some judicious whitespace to separate | them). | | And I need to learn how to write in English more like | Hemmingway, particularly before I've digested coffee. That | last paragraph got away from me a bit. | pradn wrote: | I tried to write code with small functions and was dissuaded | from doing that at both my teams over the past few years. The | reason is that it can be hard to follow the logic if it's | spread out among several functions. Jumping back and forth | breaks your flow of thought. | | I think the best compromise is small summary comments at | various points of functions that "hold the entire thought". | rglover wrote: | Check out the try/catch and logging pattern I use in the | linked post. I added that specifically so I could identify | where errors were ocurring without having to guess. | | When I get the error in the console/browser, the path to the | error is included for me like | "[generatePasswordResetToken.setTokenOnUser] Must pass value | to $set to perform an update." | | With that, I know exactly where the error is ocurring and can | jump straight into debugging it. | Merad wrote: | Good grief, that pattern looks like it's effectively | building a stack trace by hand. Does node not provide stack | traces with errors? | rglover wrote: | It provides a stack trace but it's often unintelligible. | This makes the location of the error much clearer in | complex code. | vidarh wrote: | Yeah, there really need to be a good reason to use | catch(), and that's not it. | dcolkitt wrote: | The point of isolating abstractions is that you don't have to | jump and back and forth. You look at a function, and you | understand from its contract and calling convention you | immediately know what it does. The specific details aren't | relevant for the layer of abstraction you're looking at. | | Because of well structured abstractions, thoughtful naming | conventions, documentation where required, and extensive | testing you _trust_ that the function does what it says. If I | 'm looking at a function like commitPending(), I simply see | writeToDisk() and move on. I'm in the object representation | layer, and jumping down into the details of the I/O layers | breaks flow by moving to a different level of abstraction. | The point is I trust writeToDisk() behaves reasonably and | safely, and I don't need to inspect its contents, and | definitely don't want to inline its code. | | If you find that you frequently need to jump down the tree | from sub-routine to sub-routine to understand the high level | code, then that's a definite code smell. Most likely | something is fundamentally broken in your abstraction model. | amptorn wrote: | > "your code should read like well-written prose." | | This is a good assertion but ironically it's not Bob Martin's | line. He was quoting Grady Booch. | hackinthebochs wrote: | Absolutely this. Code should tell a story, the functions and | objects you use are defined by the context of the story at that | level of description. If you have to translate between low- | level operations to reconstruct the high level behavior of some | unit of work, you are missing some opportunities for useful | abstractions. | | Coding at scale is about managing complexity. The best code is | code you don't have to read because of well named functional | boundaries. Natural language is our facility for managing | complexity generally. It shouldn't be surprising that the two | are mutually beneficial. | watwut wrote: | Yes, that one did a lot to me too. Especially when business | logic gets complicated, I want to be able to skip parts by | roughly reading meaning of the section without seeing details. | | One long stream of commands is ok to read, if you are author or | already know what it should do. But otherwise it forces you to | read too many irrelevant details on a way toward what you need. | lovebes wrote: | woah shots fired | hcarvalhoalves wrote: | > But mixed into the chapter there are more questionable | assertions. Martin says that Boolean flag arguments are bad | practice, which I agree with, because an unadorned true or false | in source code is opaque and unclear versus an explicit IS_SUITE | or IS_NOT_SUITE... but Martin's reasoning is rather that a | Boolean argument means that a function does more than one thing, | which it shouldn't. | | I see how this can be polemic because most code is littered w/ | flags, but I tend to agree that boolean flags can be an anti- | pattern (even though it's apparently idiomatic in some | languages). | | Usually the flag is there to introduce a branching condition | (effectively breaking "a function should do one thing") but don't | carry any semantic on it's own. I find the same can be achieved | w/ polymorphism and/or pattern-matching, the benefit being now | your behaviour is part of the data model (the first argument) | which is easier to reason about, document, and extend to new | cases (don't need to keep passing flags down the call chain). | | As anything, I don't think we can say "I recommend / don't | recommend X book", all knowledge and experience is useful. Just | use your judgment and don't treat programming books as a holy | book. | AnimalMuppet wrote: | > Usually the flag is there to introduce a branching condition | (effectively breaking "a function should do one thing")... | | But if you don't let the function branch, then the parent | function is going to have to decide which of two different | functions to call. Which is going to require the parent | function to branch. Sooner or later, _someone_ has to branch. | Put the branch where it makes the most sense, that is, where | the _logical_ "one-ness" of the function is preserved even with | the branch. | | > I find the same can be achieved w/ polymorphism and/or | pattern-matching, the benefit being now your behaviour is part | of the data model (the first argument) which is easier to | reason about, document, and extend to new cases (don't need to | keep passing flags down the call chain). | | You just moved the branch. Polymorphism means that you moved | the branch to the point of construction of the object. (And | that's a perfectly fine way to do it, in some cases. It's a | horrible way to try to deal with _all_ branches, though.) | Pattern-matching means that you moved the branch to when you | created the data. (Again, that _can_ be a perfectly fine way to | do it, in _some_ cases.) | Twisol wrote: | As far as the boolean flag argument goes, I've seen it | justified in terms of data-oriented design, where you want to | lift your data dependencies to the top level as much as | possible. If a function branches on some argument, and further | up the stack that argument is constant, maybe you didn't need | that branch at all if only you could invoke the right logic | directly. | | Notably, this argument has very little to do with readability. | I do prefer consolidating data and extracting data dependencies | -- I think it makes it easier to get a big-picture view, as in | Brook's "Show me your spreadsheets" -- but this argument is | rooted specifically in not making the machine do redundant | work. | lobstrosity420 wrote: | > As anything, I don't think we can say "I recommend / don't | recommend X book", all knowledge and experience is useful. Just | use your judgment and don't treat programming books as a holy | book. | | People don't want to go through the trouble of reading several | opposing points of view and synthesize that using their own | personal experience. They want to have a book tell them | everything they need to do and follow that blindly, and if that | ever bites them back then that book was clearly trash. This is | the POV the article seems to be written from IMHO. | Izkata wrote: | Not even that, this book gets recommended to newbies who | don't yet have the experience to read it critically like | that. | HelloNurse wrote: | There's a word, in other comments, that I expected to find: | zealots. Zealots aren't sufficiently critical, and they don't | _want_ to think for themselves; a reasonable person should be | able to, and a professional should be constantly itching to, step | back, look at code, and decide whether some refactoring or | rewriting is an improvement, taking a book like _Clean Code_ as a | source of general principles and good examples, not of rules. | | All the "bad" examples discussed in the article are rather | context dependent, representing uninspired examples or extreme | tastes in the book rather than bad or obsolete ideas. | | Shredding medium length meaningful operations into many very | small and quite ad hoc functions can reduce redundancy at the | expense of readability, which might or might not be an | improvement; a little DSL that looks silly if used in a couple of | test cases can be readable and efficient if more extensive usage | makes it familiar; a function with boolean arguments can be an | accretion of special cases, mature for refactoring or a | respectable way to organize otherwise repetitive code. | PaulHoule wrote: | Java is still popular. JDK 17 looks as much like ML (e.g. oCAML) | as they can make it as fast they can make it without breaking | things. | MrBuddyCasino wrote: | Also, the tooling and compiler speed aren't fucked like they | are in Scala or Kotlin. I like Kotlin, especially the null- | safety, but the ecosystem's quality is kinda shoddy. Everything | is just faster and less buggy in Java. | asdff wrote: | Why is it that software engineering is so against comments? | | I know nothing of clean code. When I read the link, I assumed | that clean code meant very simple and well commented code. I hit | cmd+f # and nothing came up. Not one comment saying "this | function is an example of this" or "note the use of this line | here, it does this" etc, on a blog no less where you'd expect to | see these things. The type of stuff I put in my own code, even | the code that only I read, because in two weeks I'm going to | forget everything unless I write full sentences to paragraph | comments, and spend way more time trying to get back in the zone | than the time it took me to write those super descriptive | comments in the first place. | | I hate looking at other peoples scripts because, once again, they | never write comments. Practically ever. What they do write is | often entirely useless to the point where they shouldn't have | even bothered writing those two words or whatever. Most people's | code is just keyboard diarrhea of syntax and regex and patterns | that you can't exactly google for, assuming whoever is looking at | the code has the exact same knowledge base as you, and knows | everything that you've put down into the script. Maybe it's a | side effect of CS major training, where you don't write comments | on your homework because the grader is going to know what is | happening. Stop doing that with your code and actually make a | write up to save others (and often yourself) mountains of time | and effort. | hyko wrote: | I don't understand the amount of hate that Clean Code gets these | days...it's a relatively straightforward set of principles that | can help you create a software system maintainable by humans for | a very long time. Of course it's not an engineering utopia, | there's no such thing. | | I get the impression that it's about the messengers and not the | messages, and that people have had horrible learning experiences | that have calcified into resistance to do with anything _clean_. | But valuable insights are being lost, and they will have to be | re-learned in a new guise at a later date. | mywittyname wrote: | Development trends are cyclical and even the most sound | principle has an exception. Even if something is good advice | 99% of the time, it will eventually be criticized with that 1% | of the time being used as a counter. | tuckerpo wrote: | Uncle "Literally who?" Bob claims you should separate your code | into as many small functions spread across as many classes as you | can and makes a living selling (proverbial) shovels. John Carmack | says you should keep functions long and have the business logic | all be encapsulated together for mental cohesion. Carmack makes a | living writing software. | watwut wrote: | I work with long functions right now. It does not give mental | cohesion. Instead, it makes it difficult to figure out what | author intended to happen. | cloverich wrote: | Or perhaps the length of the function is orthogonal to the | quality of the author's code. Make the function as long as | necessary to be readable and maintainable by the people most | likely to read and maintain it. But that's not a very | sellable snippet, nor a rule that can be grokked in 5 | minutes. | LorenPechtel wrote: | Short functions make it much harder for bugs to hide. | marcosdumay wrote: | Bugs favorite place to hide is in interfaces. | darksaints wrote: | Long functions make it much harder for bugs to hide. | | See what I did there? | MaxBarraclough wrote: | Carmack's writing on the proper length of functions (although | he expresses it in terms of when to inline a function): | http://number- | none.com/blow/john_carmack_on_inlined_code.htm.... | | 2014 HN discussion: | https://news.ycombinator.com/item?id=8374345 | | A choice quote from Carmack: | | > _The function that is least likely to cause a problem is one | that doesn 't exist, which is the benefit of inlining it. If a | function is only called in a single place, the decision is | fairly simple._ | | > _In almost all cases, code duplication is a greater evil than | whatever second order problems arise from functions being | called in different circumstances, so I would rarely advocate | duplicating code to avoid a function, but in a lot of cases you | can still avoid the function by flagging an operation to be | performed at the properly controlled time. For instance, having | one check in the player think code for health <= 0 && !killed | is almost certain to spawn less bugs than having KillPlayer() | called in 20 different places._ | dang wrote: | I happen to agree with you and have posted in various HN | threads over the years about the research on this, which (for | what it's worth) showed that longer functions were less error | prone. However, the snarky and nasty way that you made the | point makes the comment a bad one for HN, no matter how right | you are. Can you please not post that way? We're trying for | something quite different here: | https://news.ycombinator.com/newsguidelines.html. | | It's actually even more important to stick to the site | guidelines when you're right, because otherwise you discredit | the truth and give people a reason to reject it, which harms | all of us. | | https://hn.algolia.com/?dateRange=all&page=0&prefix=true&sor... | zomgwat wrote: | On the spectrum you've described, I'm progressively shifting | from Uncle Bob's end to Carmack's the further I get into my | career. I think of it as code density. I've found that high | density code is often easier to grok because there's less | ceremony to keep in my head (e.g. many long method names that | may or may not be named well, jumping around a bunch of files). | Of course, there's a point at which code becomes so dense that | it again becomes difficult to grok. | CuriouslyC wrote: | The type of software John writes is different (much more | conceptually challenging), and I don't recall him being as big | of a proponent of TDD (which is the biggest benefit to small | functions). | | I think the right answer depends on a number of other factors. | rantwasp wrote: | Uncle Bob makes a living selling snake oil. Which one should we | listen to? | hackinthebochs wrote: | Carmack is literally the top .1% (or higher) of ability and | experience. Not to mention has mostly worked in a field with | different constraints than most. I don't think looking to him | for general development advice is all that useful. | CyberDildonics wrote: | I think the exact opposite. | | Read the doom source code and you can see that he didn't mess | around with trying to put everything into some nonsense | function just because he has some part of a larger function | that can be scoped and named. | | The way he wrote programs even back then is very direct. You | don't have to jump around into lots of different functions | and files for no reason. There aren't many specialized data | structures or overly clever syntax tricks to prove how smart | he is. | | There also aren't attempts to write overly general libraries | with the idea that they will be reused 100 times in the | future. Everything just does what it needs to do directly. | hackinthebochs wrote: | But why should any of that be aspirational to an "average" | developer? It's like learning how to do mathematics by | copying Terrence Tao's patterns of behavior. Perhaps | Carmack's output is more a function of the programmer than | an indication of good practice for average devs. | CyberDildonics wrote: | I guess I'm still not being clear - when you read John | Carmack's programs you realize that it just isn't | necessary to do complex nonsense. | | If you take a look at the doom source code you realize | this isn't the cutting edge of mathematics, he is | cranking out great software by avoiding all that and | using high school algebra (literally and figuratively) | instead. | | While other people spin their wheels sweating over | following snake oil bob's pamphlet Carmack is making | programs that people want, source code that people want | and work that stands the test of time. | hackinthebochs wrote: | The circumstances he operated under while writing Doom | were much different than most people encounter. The | couple of people working with him on the code were all | experts in their field and have a complete understanding | of the problem space. What you seem to be identifying as | indications of unnecessary complexity of modern | development practices might really just be accident of | circumstance and the individual skill of the contributors | at the time. It is a mistake to look at the behaviors of | the unusually talented few and see takeaways to apply | more broadly. | CyberDildonics wrote: | He wrote doom by himself and worked with people on later | projects. You can look at the source yourself. What he | himself said is the exact opposite of what you are saying | - because he was able to write things directly he was | able to experiment a lot. He was able to get the easier | things working and go through trial and error on more | difficult aspects. | | If you would actually read through some of his work you | would see that it a refreshingly simple way to do thing. | No trying to hide that global data isn't global, no | unnecessary indirection etc. etc. | | > What you seem to be identifying as indications of | unnecessary complexity of modern development practices | might really just be accident of circumstance and the | individual skill of the contributors at the time | | I have no idea what this is supposed to mean. | | > It is a mistake to look at the behaviors of the | unusually talented few and see takeaways to apply more | broadly. | | This is the point you are trying to make but you just | keep repeating it without backing it up in any way. | | John Carmack used his skill to do things in a simple and | direct way. Anyone can start to imitate that immediately. | There is no invisible magic going on, he just doesn't | subscribe to a bunch of snake oil nonsense that distracts | people from writing the parts of their program that | actually do things. | honkycat wrote: | Honestly Clean Code probably isn't worth recommending anymore. | We've taken the good bits and absorbed it into best practice. I | think it has been usurped by books like "Software Engineering at | Google" and "Building Secure and Reliable Systems". | | I don't believe in being prescriptive to anyone about how they | write their code, because I think people have different | preferences and forcing someone to write small functions when | they tend to write large functions well is a unique form of | torture. Just leave people alone and let them do their job! | | I don't think it is the perfect solution, but a lot of people | assert "we can't do better, no point in trying, just write | whatever you feel like" and I think that is a degenerate | attitude. We CAN find better ways to construct and organize our | code, and I don't think we should stop trying because people | don't want to update their pull requests. | randompwd wrote: | Software Engineering at Google (for Google, by Google, | detailing issues which are issues mainly at Google) [You're not | Google] | honkycat wrote: | I've heard this before, and I agree, but don't let the name | put you off. I agree that designing and iterating for google | scale is a bad idea, but there is a lot in that book that is | applicable to all software teams. | shmiga wrote: | I was skipping those ugly Java code examples while reading and | the book made sense. | ericbarrett wrote: | Another thing Martin advocates for is not putting your name in | comments, e.g. "Fixed a bug here; there could still be | problematic interactions with subsystem foo -- ericb". He says, | "Source control systems are very good at remembering who added | what, when." (p. 68, 2009 edition) | | Rubbish! Multiple times I've had to track down the original | author of code that was auto-refactored, reformatted, changed | locations, changed source control, etc. "git blame" and such are | useless in these cases; it ends up being a game of Whodunit that | involves hours of search, Slack pings, and what not. Just put | your name in the comment, if it's documenting something | substantial and is the result of your own research and struggle. | And if you're in such a position, allow and encourage your | colleagues to do this too. | bjohnson225 wrote: | I never found people adding a name useful. | | Either the code is recent (in which case 'git blame' works | better since someone changing a few characters may or may not | decide to add their name to the file) or it's old and the | author has either left the company or has forgotten practically | everything about the code. | Cthulhu_ wrote: | I think your comment is controversial, for a number of reasons. | One, I think nobody should own code. Code should be obvious, | tested, documented and reviewed (bringing the number of people | involved to at least two), the story behind it should be either | in the git comments or referenced to e.g. a task management | system. Code ownership just creates islands. | | I mean by all means assign a "domain expert" to a PART of your | code, but no individual segment of code should belong to | anyone. | | Second: There's something to be said about avoiding churn. | Everybody loves refactoring and rewriting code, present company | included, but it muddles the version control waters. I've seen | a few github projects where the guidelines stated not to create | PRs for minor refactorings, because they create churn and | version control noise. | | Anyway, that's all "ideal world" thinking, I know in practice | it doesn't work like that. | Chris2048 wrote: | Maybe not exclusive ownership, but there are always going to | be those more familiar with a section of code that others. | | It's not really efficient to insist everyone know the | codebase equally, especially with larger codebases. | ziml77 wrote: | What is even the downside of adding a few extra characters to | the end of a comment to show who wrote it? | | And has Martin ever even worked on large, non-greenfield | projects? That's the only way I could see anyone professing | such idealism. | jen20 wrote: | > What is even the downside of adding a few extra characters | to the end of a comment to show who wrote it? | | You're right - in fact we should do this for every line of | code, so that we know of whom to ask questions! | func main() { // jen20 fmt.Println("Hello World") | // ziml77 } // jen20 | | What's the downside of adding a few extra characters!? | | Of course, this view is already available to people: `git | blame` - and it's the same for comments, so there is no need. | | The exception is "notes to future self" during the | development of a feature (to be removed before review), in | which case the most useful place for them to appear is at the | _start_ of the comment with a marker: // | TODO(jen20): also implement function X for type Y | | Then they are easy to find... | max46 wrote: | >You're right - in fact we should do this for every line of | code, so that we know of whom to ask questions! | | if you want to live in this kind of absolutism, I would | rather have the name on every line than no comments at all. | jen20 wrote: | Good news then: git blame already does this, and modern | editors show it all the time (via copying Code Lens | features from Visual Studio). | | Install the appropriate extension for GitHub and you can | have it there too, with no extra effort or maintenance | burden of duplicative metadata. | ericbarrett wrote: | What you've shown is code clutter and _reductio ad | absurdum_ to what I wrote in the top-level comment. I am | speaking of architectural comments, bug-fixes, and | especially in service architecture where unusual and | catastrophic interactions might happen (or have happened) | with code that 's not under your control. | nemetroid wrote: | It does require some rigor in your version control practices, | but I'm happily "git blame"-ing twenty year old code every now | and then. | jacquesm wrote: | Better put such a long explanation there that your name isn't | needed any more. Because if it is your name that makes the | difference chances are that you have left the company by the | time someone comes across that comment and needs access to your | brain. | ericbarrett wrote: | Sometimes what is interesting is that you have found that | another engineer--whom you might not know in a large enough | organization--has put time and thought into the code you are | working on, and you can be a lot more efficient and less | likely to break things if you can talk to that engineer | first. It's not always the comment itself. | jacquesm wrote: | Sure, but in IT the rule of assumption should be that the | code will outlive the coder. If being able to talk to other | engineers is going to make the difference (instead of just | being an optimization) then you already have problems. | ericbarrett wrote: | Talking to other engineers is _necessary_ when your | codebase, organization, and architecture is large enough | that changes can have far-reaching effects. I would say, | in fact, that it 's the most important distinction | between a junior and senior engineer. | | Let me give you a real-world example. Facebook used to | have a service called BCF++; it was basically a "lookup" | service for a given host, where you could do forward- or | reverse-lookups of a hostname, or a class of hosts, and | get information about their physical location, network | properties, hardware configuration, and so forth. | | This code was _old_. It had survived the transition from | SVN to Git, and I 'm sure it has in some form survived | the transition from Git to Mercurial, though that was | after my time. It had also been moved several times as no | team formally owned the service. It was originally | slapped together under extreme pressure by a few | engineers, basically a "hackathon." Despite that, it was | so useful that it had been adopted by pretty much every | team that touched infrastructure, which at the time I | became involved was ~300 engineers. | | I was working on a service that made extensive use of the | data in BCF. There was a problem with one of its Thrift | RPCs which required a bugfix. This bug had plagued users | of the service for several years, but because the code | was so old and hoary--it didn't even have auto-generation | of its Thrift bindings--nobody had bothered to fix it. | Instead, every team who used this RPC had coded around | it, or (worse still) skipped the binding and queried the | backing database directly. | | Well, I was determined to fix the bug. "git blame" showed | a bot. No problem, let's go back before that | commit...another bot. Before that, a human! Cool, let's | reach out--no, turns out he'd done some code formatting | on it. Before that--whoops, the beginning of the code | history! OK, so check the old SVN repo. Five contributors | over its history. I pinged each and every one who was | still at the company--they hadn't written it. Finally got | ahold of somebody who said, "Oh yeah, Samir++ wrote that, | ask him." I looked up and realized I could see the back | of Samir's head, because he sat 10m away. It took two | hours to get to that point, and 5 minutes to sort out | what I needed from him without literally bringing down | the site. I fixed the bug. | | Every single one of those things, of course, defied the | "rule of assumption." But every single one of those | things was done under a specific kind of duress: keeping | the company running and the features rolling. The real | world is messy, and putting a little extra in your | comments and leaving threads for future engineers is an | enormously powerful lubricant. | | ++ names changed to protect the guilty | jacquesm wrote: | I think you just bolstered my 'then you already have | problems' argument ;) | ericbarrett wrote: | Every company has these problems, my friend. Like I said, | good comments are lubrication against the problems that | _will_ arise in sufficiently large, churning code. | tacon wrote: | Yes, that is exactly the thesis of the classic paper | "Programming as Theory Building"[0], that it is the theory | in the human's head that we pay for and that the complete | history of the code is often not enough to effectively | modify a program. | | [0]https://pages.cs.wisc.edu/~remzi/Naur.pdf | watwut wrote: | If you have to track down the author, then it is already bad. | The code should not hope that the author never finds a new job. | throw1234651234 wrote: | A 1000 times this. We never use git blame - who cares? The | code should be self-explanatory, and if it's not, the author | doesn't remember why they did it 5 years down the line | either. | cloverich wrote: | But sometimes it is bad, and not fixable within the author's | control. I occasionally leave author notes, as a shortcut. If | I'm no longer here, yeah you gotta figure it all out the hard | way. But if I am, I can probably save you a week, maybe a | month. And obviously if its something you can succintly | describe, you'd just leave a comment. This is the domain of | "Based on being here a few years on a few teams, and three | services between this one, a few migrations etc etc". Some | business problems have a lot of baggage that aren't easily | documented or described, its the hard thing about | professional development especially in a changing business. | There's also cases where I _didnt'_ author the code, but did | purposefully not change something that looks like it should | be changed. In those cases, without my name comment, git | blame wouldn't point you to me. YMMV. | SassyGrapefruit wrote: | I think most of what martin says is rubbish, but this is not. I | have never had `git blame` fail...ever. I know what user is | responsible for every line of code. Doing this is | contemporaneous. Its right up there with commenting out blocks | of code so you don't lose them. | ericbarrett wrote: | I don't know what to say, this is a real problem I have | encountered in actual production code multiple times. Any | code that lives longer than your company's source control of | choice, style of choice, or code structure of choice is | vulnerable. Moreover, what's the harm? It's _more_ | information, not less. | SassyGrapefruit wrote: | >Moreover, what's the harm? It's more information, not less | | If your code is outliving your source control system you | got bigger problems than whatever is in your comments. I | can confidently say that in 25 years in this industry this | isn't something I've encountered. So probably sufficiently | low enough probability to safely ignore. | | >Moreover, what's the harm? It's more information, not | less. | | Too much information is every bit as bad as too little. | NortySpock wrote: | > If your code is outliving your source control system | you got bigger problems than whatever is in your | comments. | | You've never been at a company that finds it needs to | upgrade from $old_version_control_sytem to | $newer_version_control_system? | | Because I've never seen that done as a big-bang rollout, | it's always been "oh that code lives in the new system | now, and we only kept 6 months of history" | | Even just having an architect manually move folders | around in the version control system has broken the | history. | SassyGrapefruit wrote: | >You've never been at a company that finds it needs to | upgrade from $old_version_control_sytem to | $newer_version_control_system? | | I've done this maybe a dozen times. Its always been big | bang and I've never lost any history I didn't choose to | lose. In fact I just did this last week for a 10 year old | project that moved from SVN to GIT. | | If you work somewhere where someone tells you this isn't | possible consider finding a new job or alternatively | become their new source control lord with your new found | powers. Moving things between SCM systems is about the | easiest thing you can do. Its all sequentially structured | and verifiable. The tools just work everytime. | triceratops wrote: | > I can confidently say that in 25 years in this industry | this isn't something I've encountered. | | I've been in the industry less than half that time and | it's happened to me. blame will tell you the last | person/commit to touch that line of code. To find out | when it was originally written, I may have to (in some | cases) manually binary search the history of the file. | SassyGrapefruit wrote: | why? Just go to the commit hash in the blame and run | blame again. | | All in all it has a negligible impact on the readability | of the code. It's mostly aesthetic for me. It's ugly and | only solves far fetched problems. | | Do you scratch your name and SSN into the side of your | car? What if your title blows away in the wind on the | same day that city hall burns down destroying all | ownership records? | richardxia wrote: | Was going to post the exact same thing. I make use of | this repeated git blame method all the time, and for | everyone who is just learning this for the first time, | you'll actually want to write `git blame <commit>~` to go | back one commit from the commit hash in the blame, | because otherwise you'll still get the same results on | the line you're looking at. | | Also, if you're using GitHub, their Blame view also a | button that looks like a stack of rectangles between the | commit information and the code. Clicking that will | essentially do the same thing command-line git operation | above. | erik_seaberg wrote: | git blame --ignore-rev also helps if people or bots are | contaminating your history with meaningless changes. | triceratops wrote: | Good idea. TIL. | | (I'm embarrassed I didn't think of it before) | nemetroid wrote: | If you're a CLI user, you should check out tig, which is | a terminal UI for git. In the blame view, you can select | a line and press "," to go to the blame view for the | _parent_ of the commit blamed for that line. | | This lets you quickly traverse through the history of a | file. Another good tool for digging into the history is | the -S flag to "git log", which finds commits that added | or removed a certain string (there's -G that searches for | a regex, too). | jimpudar wrote: | If you prefer a GUI tool, DeepGit[0] is a very nice tool | that allows you to do some pretty amazing code | archeology. I use this all the time for figuring out how | legacy code evolved over time. | | [0] https://www.syntevo.com/deepgit/ | gher-shyu3i wrote: | The parent's comment holds when reformatting, especially in | languages with suspect formatting practices like golang, | where visibility rules are dictated by the case of the first | letter (wat?) or how it attempts to align groups of constants | or struct fields depending on the length of the longest field | name. Ends up in completely unnecessary changes that divert | away from the main diff. | triceratops wrote: | Code never gets moved or refactored by someone other than the | original author? | [deleted] | kapp_in_life wrote: | So then you checkout at the refactor commit and look | through the blame to continue searching. If you have to | repeat this more than a few times then the person has | probably left the company or hasn't touched the code in | years so its better to understand it yourself before | modifying. | triceratops wrote: | I'm embarrassed I didn't think of that before. | SassyGrapefruit wrote: | If it gets moved then the blame will tell me who moved it, | It will also tell me what the hash was before it was moved. | That hash will have all the original information. Same for | the refactor case. | hackinthebochs wrote: | There's a lot of bad advice being tossed around in this thread. | If you are worried about having to jump through multiple files to | understand what some code is doing, you should consider that your | naming conventions are the problem, not the fact that code is | hidden behind functional boundaries. | | Coding at scale is about managing complexity. The best code is | code you don't have to read because of well named functional | boundaries. Without these functional boundaries, you have to | understand how every line of a function works, and then mentally | model the entire graph of interactions at once, because of the | potential for interactions between lines within a functional | boundary. The complexity (sum total of _possible_ interactions) | grows as the number of lines within a functional boundary grows. | The cognitive load to understand code grows as the number of | _possible_ interactions grow. Keeping methods short and hiding | behavior behind well named functional boundaries is how you | manage complexity in code. | | The idea of code telling a story is that a unit of work should | explain what it does through its use of well named variables, | function/object names, and how data flows between | function/objects. If you have to dig into the details of a | function to understand what it does, you have failed to | sufficiently explain what the function does through its naming | and set of arguments. | superjan wrote: | But not everybody codes "at scale". If you have a small, stable | team, there is a lot less to worry about. | | Secondly it is often better to start with less abstractions and | boundaries, and add them when the need becomes apparent, rather | than trying to remove ill conceived boundaries and abstractions | that were added at earlier times. | mywittyname wrote: | Well named functions are only half (or maybe a quarter) of the | battle. Function documentation is paramount in complex | codebases, since documentation should describe various | parameters in detail and outline any known issues, side- | effects, or general points about calling the function. It's | also a good idea to document when a parameter is passed to | another function/method. | | Yeah, it's a lot of work, but working on recent projects have | really taught me the value of good documentation. Naming a | function send_records_to_database is fine, but it can't tell | you how it determines which database to send the records to, or | how it deals with failed records (if at all), or various | alternative use cases for the function. All of that must come | from documentation (or reading the source of that function). | | Plus, I've found that forcing myself to write function | documentation, and justify my decisions, has resulted in me | putting more consideration into design. When you have to say, | "this function reads <some value> name from <environmental | variable>" then you have to spend some time considering if | future users will find that to be a sound decision. | eweise wrote: | Yikes, I hope I don't have to read documentation to | understand how the code deals with failed records or other | use cases. Good code would have the use cases separated from | the send_records_to_database so it would be obvious what the | records were and how failure conditions are handled. | mywittyname wrote: | How else are you going to understand how a library works | besides RTFM or RTFC? I guess the third option is copy | pasta from stack overflow and hope your use case doesn't | require any significant deviation? | | You seriously never have to read documentation? | | Must be nice, I've been balls-deep in GCP libraries and | even simple things like pulling from a PubSub topic have | footguns and undocumented features in certain library | calls. Like subscriber.subscribe returns a future that | triggers a callback function for each polled message, while | subscriber.pull returns an array of messages. | | That's a pretty damn obvious case where functions should | have been named "obviously" (pull_async, pull_sync), yet | they weren't. And that's from a very widely used service | from one of the biggest tech companies out there, written | by a person that presumably passed one of the hardest | interviews in the industry and gets paid in the top like 1% | of developer. | | Without documentation, I would have never figured those | out. | cratermoon wrote: | > documentation should describe various parameters in detail | and outline any known issues, side-effects, or general points | about calling the function. It's also a good idea to document | when a parameter is passed to another function/method. | | I'd argue that writing that much documentation about a single | function suggests that the function is a problem and the | "send_records_to_database" example is a bad name. It's almost | inevitable that the function doing so much and having so much | behavior that needs documentation will, at some point, be | changed and make the documentation subtly wrong, or at least | incomplete. | danShumway wrote: | What's the alternative? Small functions get used in other | functions. Eventually you end up with a function everyone's | calling that's doing the same logic, just itself calling | into smaller functions to do it. | | You can argue that there should be separate functions for | `send_to_database` and `lock_database` and | `format_data_for_database` and `handle_db_error`. But | you're still going to have to document the same stuff. | You're still going to have to remind people to lock the | database in some situations. You're still going to have to | worry about people forgetting to call one of those | functions. | | And eventually you're going to expose a single | endpoint/interface that handles an entire database | transaction including stuff like data sanitation and error | handling, and then you're going to need to document that | endpoint/interface in the same way that you would have | needed to document the original function. | bluGill wrote: | Documentation is only useful it is up to date and correct. I | ignore documentation because I've never found the above are | true. | | There are contract/proof systems that seem like they might | work help. At least the tool ensures it is correct. However | I'm not sure if such systems are readable. (I've never used | one in the real world) | fouric wrote: | Yes, coding at scale is about managing complexity. No, "Keeping | methods short" is _not_ a good way to manage complexity, | because... | | > then mentally model the entire graph of interactions at once | | ...partially applies even if you have well-named functional | boundaries. You said it yourself: | | > The complexity (sum total of possible interactions) grows as | the number of lines within a functional boundary grows. The | cognitive load to understand code grows as the number of | possible interactions grow. | | Programs have a certain essential complexity. Making a function | "simpler" means making it less complex, which means that that | complexity has to go somewhere else. If you make all of your | functions simple, then you simply need more functions to | represent the same program, which increases the total number of | possible interactions between nodes and therefore the cognitive | load of understanding the _whole_ graph /program. | | Allowing more complexity in your functions makes them | individually harder to understand, but reduces the total number | of functions needed and therefore makes the _entire program | more comprehensible_. | | Also note that just because a function's _implementation_ is | complex doesn 't mean that its _interface_ also has to be | complex. | | And, functions with complex implementations are only themselves | difficult to understand - functions with complex interfaces | make the whole _system_ more difficult to understand. | TheOtherHobbes wrote: | This is where Occam's Razor applies - _do not multiply | entities unnecessarily._ | | Having hundreds or thousands of simple functions is the | opposite of this advice. | | You can also consider this in more scientific terms. | | Code is a _mental model_ of a set of operations. The best | possible model has as few moving parts as possible, there are | as few connections between the parts as possible, each part | is as simple as possible, and both the parts and the | connections between them are as intuitively obvious as | possible. | | Making parts as simple as possible is just one design goal, | and not a very satisfactory or useful one in its own terms. | | All of this turns out to be incredibly hard, and is a literal | IQ test. Mediocre developers will always, _always_ create | overcomplicated solutions. Top developers have a magical | ability to combine a 10,000 foot overview with ground level | detail, and will tear through complex problems and reduce | them to elegant simplicity. | | IMO we should spend less time teaching algorithms and testing | algorithmic specifics, and more on analysing complex systems | and implementing them with minimal, elegant, intuitive | models. | hackinthebochs wrote: | >If you make all of your functions simple, then you simply | need more functions to represent the same program | | The semantics of the language and the structure of the code | help hide irrelevant functional units from the global | namespace. Methods attached to an object only need to be | considered when operating on some object, for example. | Private methods do not pollute the global namespace nor do | they need to be present in any mental model of the | application unless it is relevant to the context. | | While I do think you can go too far with adding functions for | its own sake, I don't see that they add to the cognitive load | in the same way that possible interactions within a | functional unit does. If you're just polluting a global | namespace with functions and tiny objects, then that does | similarly increase cognitive load and should be avoided. | danShumway wrote: | > If you have to dig into the details of a function to | understand what it does, you have failed to sufficiently | explain what the function does through its naming and set of | arguments. | | This isn't always true in my experience. Often when I need to | dig into the details of a function it's because _how_ it works | is more important than what it says it 's doing. There are | implementation concerns you can't fit into a function name. | | Additionally, I have found that function names become outdated | at about the same rate as comments do. If the common criticism | of code commenting is that "comments are code you don't run", | function names also fall into that category. | | I don't have a universal rule on this, I think that managing | code complexity is highly application-dependent, and dependent | on the size of the team looking at the code, and dependent on | the age of the code, and dependent on how fast the code is | being iterated on and rewritten. However, in many cases I've | started to find that it makes sense to inline certain logic, | because you get rid of the risk of names going out of date just | like code comments, and you remove any ambiguity over what the | code actually does. There are some other benefits as well, but | they're beyond the scope of the current conversation. | | Perfect abstractions are relatively rare, so in instances where | abstractions are likely to be very leaky (which happens more | often than people suspect), it is better to be extremely | transparent about what the code is doing, rather than hiding it | behind a function name. | | > The complexity (sum total of possible interactions) grows as | the number of lines within a functional boundary grows. | | I'll also push back against this line of thought. The sum total | of possible interactions do not decrease when you move code out | into a separate function. The same number of lines of code | still get run, and each line carries the same potential to have | a bug. In fact, in many cases, adding additional interfaces | between components and generalizing them can increase the | number of code paths and potential failure points. | | If you define complexity by the sum total of possible | interactions (which is itself a problematic definition, but | I'll talk about that below), then complexity always _increases_ | when you factor out functions, because the interfaces, error- | handling, and boilerplate code around those functions increases | the number of possible interactions happening during your | function call. | | > The complexity (sum total of possible interactions) grows as | the number of lines within a functional boundary grows. | | What I've come to understand is that complexity is relative. A | solution that makes a codebase less complex for one person in | an organization may make a codebase more complex for someone | else in the organization who has different responsibilities | over the codebase. | | If you are building an application with a large team, and there | are clear divisions of responsibilities, then functional | boundaries are very helpful because they hide the messy details | about how low-level parts of the code work. | | However, if you are responsible for maintaining both the high- | level and low-level parts of the same codebase, than separating | that logic can sometimes make the program harder to manage, | because you still have to understand how both parts of the | codebase work, but now you also have understand how the | interfaces and abstractions between them fit together and what | their limitations are. | | In single-person projects where I'm the only person touching | the codebase I do still use abstractions, but I often opt to | limit the number of abstractions, and I inline code more often | than I would in a larger project. This is because if I'm the | only person working on the code, I need to be able to hold | almost the entire codebase in my head at the same time in order | to make informed architecture decisions, and managing a large | number of abstractions on top of their implementations makes | the code harder to reason about and increases the number of | things I need to remember. This was a hard-learned lesson for | me, but has made (I think) an observable difference in the | quality and stability of the code I write. | fouric wrote: | >> If you have to dig into the details of a function to | understand what it does, you have failed to sufficiently | explain what the function does through its naming and set of | arguments. | | > This isn't always true in my experience. Often when I need | to dig into the details of a function it's because how it | works is more important than what it says it's doing. There | are implementation concerns you can't fit into a function | name. | | Both of these things are not quite right. Yes, if you have to | dig into the details of a function to understand what it | does, it hasn't been explained well enough. No, the prototype | _cannot_ contain enough information to explain it. No, you | shouldn 't look at the implementation either - that leads to | brittle code where you start to rely on the implementation | behavior of a function that isn't part of the interface. | | The interface and implementation of a function are separate. | The former should be clearly-documented - a descriptive name | is good, but you'll almost always also need | docstrings/comments/other documentation - while you should | rarely rely on details of the latter, because if you are, | that usually means that the interface isn't defined clearly | enough and/or the abstraction boundaries are in the wrong | places (modulo things like looking under the hood to | refactor, improve performance, etc - all abstractions are | somewhat leaky, but you shouldn't be piercing them | _regularly_ ). | | > If you define complexity by the sum total of possible | interactions (which is itself a problematic definition, but | I'll talk about that below), then complexity always increases | when you factor out functions, because the interfaces, error- | handling, and boilerplate code around those functions | increases the number of possible interactions happening | during your function call. | | This - this is what everyone who advocates for "small | functions" doesn't understand. | jghn wrote: | I have found this to be one of those A or B developer personas | that are hard for someone to change, and causes much | disagreement. I personally agree 100%, but have known other | people who couldn't disagree more, it is what it is. | | I've always felt it had a strong correlation to top-down vs | bottom-up thinkers in terms of software design. The top-down | folks tend to agree with your stance and the bottom-up group do | not. If you're naturally going to want to understand all of the | nitty gritty details you want to be able to wrap your head | around those as quickly as possible. If you're willing to think | in terms of the abstractions you want to remove as many of | those details from sight as possible to reduce visual noise. | anm89 wrote: | I never thought of things this way but it is a useful | perspective. | Joker_vD wrote: | I wish there was an "auto-flattener"/"auto-inliner" tool that | would allow you to automagically turn code that was written | top-down, with lots of nicely high-level abstractions, into | an equivalent code with all the actions mushed together and | with infrastructure layers peeled away as much as possible. | | Have you ever seen a codebase with infrastructure and piping | taking about 70% of the code, with tiny pieces of business | logic thrown here and there? It's impossible to figure out | where the actual job is being done (and what it actually is): | all you can see is just an endless chain of methods that | mostly just delegate the responsibility further and further. | What could've been a 100-line loop of "foreach item in | worklist, do A, B, C" kind is instead split over seven | tightly cooperating classes that devote 45% of their code to | multiplexing/load-balancing/messaging/job-spooling/etc, | another 45% to building trivial auxiliary structure and | instantiating each other, and only 10% actually devoted to | the actual data processing, but good luck finding those 10%, | because there is a never-ending chain of calling each other: | A.do_work() calls B.process_item() which calls | A.on_item_processing() which calls B.on_processed()... wait, | shouldn't there been some work done between | "on_item_processing" and "on_processed"? Yes, it was done by | an inconspicuously named "prepare_next_worklist_item" | function. | | Ah, and the icing on the cake: looping is actually done from | the very bottom of this call chain by doing a recursive call | to the top-most method which at this point is about 20 layers | above the current stack frame. Just so you can walk down this | path again, now with the feeling. | ambicapter wrote: | > I wish there was an "auto-flattener"/"auto-inliner" tool | that would allow you to automagically turn code that was | written top-down, with lots of nicely high-level | abstractions, into an equivalent code with all the actions | mushed together and with infrastructure layers peeled away | as much as possible. | | Learn to read assembly and knock yourself out. | Jtsummers wrote: | That's not a very helpful response. Unless the code is | compiled to native machine code and is all inlined, this | won't help one bit. | nytgop77 wrote: | Maybe not helpfull, but it made me smile :-) | AtlasBarfed wrote: | On today's HN with this thread is "the hole in | mathematics". | | It is directly germane to what you are talking about. | | In the process of formalizing axiomatic math, 1+1=2 took | 700 pages in a book to formally prove. | | The point about assembly is more or less correct. The | process of de-abstracting is going to be long and | probably not that clear in the end. | | I understand what you mean: the assembly commenter is | correct, you'll need to actually execute the program and | reduce it to a series of instructions it actually | performed. | | Which is either an actual assembly, or a pseudo-assembly | instruction stream for the underlying turing machine: | your computer. | vikiomega9 wrote: | Most editors have code folding. I've noticed this helps | when there are comments or it's easy to figure out the | branching or what not. | | However, what you're asking for is a design style that's | hard to implement I think without language tooling (for | example identifying effectful methods). | dcolkitt wrote: | The point is that bottom-up code is a siren song. It never | scales. It makes it a lot easier to get started, but given | enough complexity it inevitably breaks down. | | Once your codebase gets to somewhere around the 10,000 line | mark, it becomes impossible for a single mind to hold the | entire program in their head at a single time. The only way | to survive past that point is with carefully thought out, | water tight layers of abstractions. That almost never happens | with bottom-up. Bottom-up is a lot like natural selection. | You get a lot of kludges that work great to solve their | immediate problem, but behave in undefined and unpredictable | ways when you extend them outside their original environment. | | Bottom-up can work when you're inside well-encapsulated | modular components with bounded scope and size. But there's | no way to keep those modules loosely coupled unless you have | a elegant top-down architecture imposing order at the large- | scale structure. | danShumway wrote: | But the reverse is also true. Top-down programming doesn't | really work well for smaller programs, it definitely | doesn't work well when you're dealing with small, highly | performance-critical or complex tasks. | | So sure, I'll grant that when your program reaches the | 10,000 line mark, you need to have some serious | abstractions. I'll even give you that you might need to | start abstracting things when a file reaches 1,000 lines. | | But when we start talking about the rule of 30 -- that's | not managing complexity, that's alphabetizing a sock drawer | and sewing little permanent labels on each sock. That | approach _also_ doesn 't scale to large programs because it | makes rewrites and refactors into hell, and it makes new | features extremely cumbersome to quickly iterate on. Your | 10,000 line program becomes 20,000 lines because you're | throwing interfaces and boilerplate all over the place. | | Note that this isn't theoretical, I have worked in programs | that did everything from building an abstraction layer over | the database in case we wanted to use Mongo and SQL at the | same time (we didn't), to having a dependency management | system in place that meant we had to edit 5 files every | time we wanted to add a new class, to having a page | lifecycle framework that was so complicated that half of | our internal support requests were trying to figure out | when it was safe to start adding customer data to the page. | | The benefit of a good, long, single-purpose function that | contains all of its logic in one place is that you know | exactly what the dependendencies are, you know exactly what | the function is doing, you know that no one else is calling | into the inlined logic that you're editing, and you can | easily move that code around and change it without worrying | about updating names or changing interfaces. | | Abstract your code, but abstract your code when or shortly | before you hit complexity barriers and after you have | enough knowledge to make informed decisions about which | abstractions will be helpful -- don't create a brand new | interface every time you write a single function. It's fine | to have a function that's longer than a couple hundred | lines. If you're building something like a rendering or | update loop, in many cases I would say it's preferable. | _dps wrote: | As mainly a bottom-up person, I completely agree with your | analysis but I wonder if you might be using "top-down | architecture" here in an overloaded way? | | My personal style is bottom up, maximally direct code, | aiming for monolithic modules under 10kloc, combined with | module coupling over very narrow interfaces. Generally the | narrow interfaces emerge from finding the "natural grain" | of the module after writing it, not from some a priori top- | down idea of how the communication pathways should be | shaped. | | Edit: an example of a narrow interface might be having a | 10kloc quantitative trading strategy module that | communicates with some larger system only by reading off a | queue of things that might need to be traded, and writing | to a queue of desired actions. | Chris_Newton wrote: | I'm reminded of an earlier HN discussion about an article | called _The Wrong Abstraction_ , where I argued1 that | abstractions have both a benefit and a cost and that their | ratio may change as a program evolves and which of those | "nitty gritty details" are immediately relevant and which can | helpfully be hidden behind abstractions changes. | | 1 https://news.ycombinator.com/item?id=23742118 | ska wrote: | While I think you are onto something about top-down vs. | bottom-up thinkers, one of the issues with a large codebase | is literally nobody can do the whole thing bottom-up. So you | need some reasonable conventions and abstraction, or the | whole thing falls apart under it's own weight. | jghn wrote: | Yep, absolutely. | | That's another aspect of my grand unifying theory of | developers. Those same personas seem to have correlations | in other ways: dynamic vs static typing, languages, | monolith vs micro service. How one perceives complexity, | what causes one to complain about complexity, etc all vary | based on these things. It's easy to arrive in circumstances | where people are arguing past each other. | | If you _need_ to be able to keep all the details in your | head you 're going to need smaller codebases. Similar, if | you're already keeping track of everything, things like | static typing become less important to you. And the | opposite is true. | danShumway wrote: | Huh. There's something to this. | | I've often wondered why certain people feel so attached | to static typing when in my experience it's rarely the | primary source of bugs in any of the codebases I work | with. | | But it's true, I do generally feel like a codebase that's | so complex or fractured that no one can understand any | sizable chunk of it is just already going to be a | disaster regardless of what kind of typing it uses. I | don't hate microservices, they're often the right | decision, but I feel they're almost always more | complicated than a monolith would be. And I do regularly | end up just reading implementation code, even in 3rd- | party libraries that I use. In fact in some libraries, | sometimes reading the source is quicker and more reliable | than trying to find the relevant documentation. | | I wouldn't extrapolate too much based on that, but it's | interesting to hear someone make those connections. | [deleted] | paiute wrote: | "There are two ways of constructing a software design: One way | is to make it so simple that there are obviously no | deficiencies, and the other way is to make it so complicated | that there are no obvious deficiencies. The first method is far | more difficult." -- C. A. R. Hoare | | this quote scales | fouric wrote: | This quote does not scale. Software contains essential | complexity because _it was built to fulfill a need_. You can | make all of the beautiful, feature-impoverished designs you | want - they won 't make it to production, and I won't use | them, because _they don 't do the thing_. | | If your software does not do the thing, then it's not useful, | it's a piece of art - not an artifact of _software | engineering_ that is meant to fulfill a purpose. | gnuvince wrote: | > The idea of code telling a story is that a unit of work | should explain what it does through its use of well named | variables, function/object names, and how data flows between | function/objects. | | Code telling a story is a fallacy that programmers keep telling | themselves and which fails to die. Code doesn't tell stories, | programmers do. Code can't explain why it exists; it can't tell | you about the buggy API it relies on and which makes its | implementation weird and not straight-forward; it can't say | when it's no longer needed. | | Good names are important, but it's false that having well- | chosen function and arguments names will tell a programmer | everything they need to know. | hackinthebochs wrote: | >Code doesn't tell stories, programmers do. Code can't | explain why it exists; | | Code can't tell every relevant story, but it can tell a story | about how it does what it does. Code is primarily written for | other programmers. Writing code in such a way that other | people with some familiarity with the problem space can | understand easily should be the goal. But this means telling | a story to the next reader, the story of how the inputs to | some functional unit are translated into its outputs or | changes in state. The best way to explain this to another | human is almost never the best way to explain it to a | computer. But since we have to communicate with other humans | and to the computer from the same code, it takes some effort | to bridge the two paradigms. Having the code tell a story at | the high level by way of the modules, objects and methods | being called is how we bridge this gap. But there are better | and worse ways to do this. | | Software development is a process of translating the natural | language-spec of the system into a code-spec. But you can | have the natural language-spec embedded in the structure of | the code to a large degree. The more, the better. | dsego wrote: | Is code just a byproduct of specs then? Any thoughts on | literate programming? | loopz wrote: | Literate programming is for programs that is static and | don't ever change much. Works great for those cases though. | | No, what works is the same that worked 20 years ago. | Nothing have truly changed. You still have layers upon | layers, that sometimes pass something, othertimes not, and | you sometimes wished it passed something, othertimes not. | barrkel wrote: | Function names are comments, and have similar failure modes. | smolder wrote: | There's certainly some difference in priorities between massive | 1000-programmer projects where complexity must be aggressively | managed and, say, a 3-person team making a simple web app. | Different projects will have a different sweet spot in terms of | structural complexity versus function complexity. I've seen | code that, IMO, misses the sweet spot in either direction. | | Sometimes there is too much code in mega-functions, poor | separation of concerns and so on. These are easy mistakes to | make, especially for beginners, so there are a lot of warnings | against them. | | Other times you have too many abstractions and too much | indirection to serve any useful purpose. The ratio of named | things, functional boundaries, and interface definitions to | actual instructions can easily get out of hand when people | dogmatically apply complexity-managing patterns to things that | aren't very complex. Such over-abstraction can fall under YAGNI | and waste time/$ as the code becomes slower to navigate, slower | to understand _in depth_ , and possibly slower to modify. | | I think in software engineering we suffer more from the former | problem than the latter problem, but the latter problem is | often more frustrating because it's easier to argue for | applying nifty patterns and levels of indirection than omitting | them. | | Just for a tangible example: If I have to iterate over a 3D | data structure with an X Y and Z dimension, and use 3 nested | loops to do so, is that too complex a function? I'd say no. | It's at least as clear without introducing more functional | boundaries, which is effort with no benefit. | jzoch wrote: | > you have failed to sufficiently explain | | This is the problem right here. I don't just read code I've | written and I don't only read perfectly abstracted code. When I | am stuck reading someone's code who loves the book and tries | their best to follow those conventions I find it far more | difficult - because I am usually reading their code to fully | understand it myself (ie in a review) or to fix a bug I find it | infuriating that I am jumping through dozens of files just so | everything looks nice on a slide - names are great, I fully | appreciate good naming but pretending that using a ton of extra | files just to improve naming slightly isnt a hindrance is wild. | | I will take the naming hit in return for locality. I'd like to | be able to hold more than 5 lines of code in my head but | leaping all over the filesystem just to see 3 line or 5 line | classes that delegate to yet another class is too much. | gentleman11 wrote: | Carmack once suggested that people in-line their functions | more often, in part so they could "see clearly the full | horror of what they have done" (paraphrased from memory) as | code gets more complicated. Many helper functions can be | replaced by comments and the code inlined. I tried this last | year and it led to overall more readable code, imho. | derangedHorse wrote: | The idea is that without proper boundaries, finding the line | that needed to be changed may be a lot harder than clicking | through files with an IDE. Smaller components also help with | code reviews since it's a lot easier to understand a line | within the context of a component (or method name) without | having to understand what the huge globs of code before it is | doing. Also, like you said a lot of the times a developer has | to read code they didn't write so there are other factors to | consider like how easy it is for someone from another team to | make a change or whether a new employee could easily digest | the code base. | lumost wrote: | > Clicking through files with an IDE | | This is a big assumption. Many engineers prefer to grep | through code without an IDE, the "clean code" style breaks | grep/github code search and forces someone to install an | IDE with go to declaration/find usages. On balance I prefer | the clean code style and bought the jetbrains ultimate | pack, however I do understand that some folks are working | with grep/vim/code search and would rather not download a | project to figure out how it works. | ajuc wrote: | > The complexity (sum total of possible interactions) grows as | the number of lines within a functional boundary grows. | | That's only 1 part of the complexity equation. | | When you have 100 lines in 1 function you know exactly the | order in which each line will happen and under which conditions | by just looking at it. | | If you split it into 10 functions 10-lines-long each now you | have 10! possible orderings of calling these functions | (ignoring loops and branches). And since this ordering is | separated into multiple places - you have to keep it in your | mind. Good luck inventing naming that will make obvious which | of the 3628800 possible orderings is happening without reading | through them. | | Short functions are good when they fit the problem. Often they | don't. | jjnoakes wrote: | I feel like this is only a problem if the small functions | share a lot of global state. If each one acts upon its | arguments and returns values without side effects, ordering | is much less of an issue IMO. | ajuc wrote: | Well, if they were one function before they probably share | some state. | | Clean code recommends turning that function into a class | and promoting the shared state from local variables into | fields. After such a "refactoring" you get a nice puzzle | trying to understand what exactly happens. | TeMPOraL wrote: | > _The idea of code telling a story is that a unit of work | should explain what it does through its use of well named | variables, function /object names, and how data flows between | function/objects. If you have to dig into the details of a | function to understand what it does, you have failed to | sufficiently explain what the function does through its naming | and set of arguments._ | | That's fine in theory and I still sort-of believe that, but in | practice, I came to believe most programming languages are | insufficiently expressive for this vision to be true. | | Take, as a random example, this bit of C++: | //... const auto foo = Frobnicate(bar, Quuxify); | | Ok, I know what Frobnification is. I know what Quuxify does, | it's defined a few lines above. From that single line, I can | guess it Frobs every member of bar via Quuxify. But is bar | modified? Gotta check the signature of Frobnicate! That means | either getting an IDE help popup, or finding the declaration. | template<typename Stuffs, typename Fn> auto | Frobnicate(const std::vector<Stuffs>&, Fn) -> | std::vector<Stuffs>; | | From the signature, I can see that bar full of Bars isn't going | to be modified. But then I think, is foo.size() going to be | equal to bar.size()? What if bar is empty? Can Frobnicate throw | an exception? Are there any special constraints on the function | Fn passed to it? Does Fn have to be a funcallable thing? Can't | tell that until I pop into _definition_ of Frobnicate. | | I'll omit the definition here. But now that I see it, I realize | that Fn has to be a function of a very particular signature, | that Fn is applied to every other element of the input vector | (and not all of them, as I assumed), that the code has a bug | and will crash if the input vector has less than 2 elements, | and it calls three other functions that may or may not have | their own restrictions on arguments, and may or may not throw | an exception. | | If I don't have a fully-configured IDE, I'll likely just ignore | it and bear the risk. If I have, I'll routinely jump-to- | definition into all these functions, quickly eye them for any | potential issues... and, if I have the time, I'll put a comment | on top of Frobnicate declaration, documenting everything I just | learned - because holy hell, I don't want to waste my time | doing the same thing next week. I would rename the function | itself to include extra details, but then the name would be | 100+ characters long... | | Some languages are better at this than others, but my point is, | until we have programming languages that can (and force you to) | express the entire function contract in its signature _and_ | enforce this at compile-time, it 's unsafe to assume a given | function does what you think it does. Comments would be a | decent workaround, _if_ most programmers could be arsed to | write them. As it is, you have to dig into the implementation | of your dependencies, at least one level deep, if you want to | avoid subtle bugs creeping in. | skybrian wrote: | This is true but having good function names will at least | help you avoid going _two_ levels deep. Or N levels. Having a | vague understanding of a function call's purpose from its | name helps because you have to trim the search tree | somewhere. | | Though, if you're in a nest of tiny forwarding functions, who | knows how deep you'll have to go? | TeMPOraL wrote: | > _having good function names will at least help you avoid | going two levels deep. Or N levels._ | | I agree. You have to trim your search space, or you'll | never be able to do anything. What I was trying to say is, | I don't know of the language that would allow you to _only | ever_ rely on function names /signatures. None that I | worked could do that in practice. | | > _if you're in a nest of tiny forwarding functions, who | knows how deep you'll have to go?_ | | That's the reason I hate the "Clean Code"-ish pattern of | lots of very tiny functions. I worked in a codebase written | in this style, and doing anything with it felt like it was | 90% jumping around function definitions, desperately trying | to keep them all in my working memory. | skybrian wrote: | I think part of the problem is imitating having | abstraction boundaries without actually doing the work to | make a clean abstraction. If you're reading the source | code of a function, the abstraction is failing. | | The function calls you write will often "know too much," | depending on implementation details in a way that make | the implementation harder to change. It's okay if you can | fix all the usages when needed. | | Real abstraction boundaries are expensive and tend only | to be done properly out of necessity. (browser API's, | Linux kernel interface.) If you're reading a browser | implementation instead of standards docs to write code | then you're doing it wrong since other browsers, or a new | version of the same browser, may be different. | | Having lots of fake abstraction boundaries adds | obfuscation via indirection. | nytgop77 wrote: | One more angle: reliable & internalized abstraction vs | unfamiliar one. | | Java string is abstraction over bytes. I feel I | understand it intimately even though I have not read the | implementation. | | When I try to understand code fully (searching for root | cause), and there is String.format(..), I don't dig | deeper into string - I already am confident that I | understand what that line does. | | Browser and linux api I guess would fall into same | category (for others). | | Unfamiliar abstraction even with seemingly good naming | and documentation, will not cause same level of | confidence. (I trust unfamiliar abstraction naming&docs | the same way I trust weather forecast) | TeMPOraL wrote: | I think it may be harder still: typically, when writing | against a third-party API, I usually consult that API's | _documentation_. The documentation thus becomes a part of | the abstraction boundary, a part that isn 't expressed in | code. | hackinthebochs wrote: | This is a good point and I agree. In fact, I think this | really touches on why I always had a hard time understanding | C++ code. I first learned to program with C/C++ so I have no | problem writing C++, but understanding other people's code | has always been much more difficult than other languages. Its | facilities for abstraction were (historically) subpar, and | even things like aliased variables where you have to jump to | the function definition just to see if the parameter will be | modified really get in the way of easy comprehension. And | then the nested template definitions. You're right that how | well relying on well named functional boundaries works | depends on the language, and languages aren't at the point | where it can be completely relied on. | bumby wrote: | > _Coding at scale is about managing complexity._ | | I would extend this one level higher to say managing complexity | is about managing risk. Risk is usually what we really care | about. | | From the article: | | > _any one person 's opinions about another person's opinions | about "clean code" are necessarily highly subjective._ | | At some point CS _as a profession_ has to find the right | balance of art and science. There 's room for both. Codifying | certain standards is the domain of professions (in the truest | sense of the word) and not art. | | Software often likens itself to traditional engineering | disciplines. Those traditional engineering disciplines manage | risk through codified standards built through industry | consensus. Somebody may build a pressure system that doesn't | conform to standards. They don't get to say "well your idea of | 'good' is just an opinion so it's subjective". By | "professional" standards they have built something outside the | acceptable risk envelope and, if it's a regulated engineering | domain, they can't use it. | | This isn't to mean a coder would have to follow rigid rules | constantly or that it needs a regulatory body, but that the | practice of deviating from standardized best-practices should | be communicated in terms of the risk rather than claiming it's | just subjective. | 908B64B197 wrote: | A lot of "best practices" in engineering were established | empirically, after root cause analysis of failures and | successes. Software is more or less evolving along the same | path (structured programming, OOP, higher-than-assembly | languages, version control, documented ISAs). | | Go back to earlier machines and each version had it's own | assembly language and instruction set. Nobody would ever go | back to that era. | | OOP was pitched as a one-size-fits-all solution to all | problems, and as a checklist of items that would turn a cheap | offshored programmer into a real software engineer thanks to | design patterns and abstractions dictated by a "Software | Architect". We all know it to be false, and bordering on | snake oil, but it still had some good ideas. Having a class | encapsulate complexity and defining interfaces is neat. It | forces to think in terms of abstractions and helps | readability. | | > This isn't to mean a coder would have to follow rigid rules | constantly or that it needs a regulatory body, but that the | practice of deviating from standardized best-practices should | be communicated in terms of the risk rather than claiming | it's just subjective. | | As more and more years pass, I'm less and less against a | regulatory body. Would help with getting rid of snake oil | salesman in the industry and limit offshoring to barely | qualified coders. And simplify hiring too by having a known | certification that tells you someone at least meets a certain | bar. | javajosh wrote: | Software is to alchemy what software engineering is to | chemistry. _Software engineering hasn 't been invented | yet_. You need a systematizing scientific revolution (Kuhn | style) before you can or should create a regulatory body to | enforce it. Otherwise you're just enforcing your particular | brand of alchemy. | bumby wrote: | > _having a known certification that tells you someone at | least meets a certain bar._ | | This was tried a few years back by creating a Professional | Engineer licensure for software but it went away due to | lack of demand. It could make sense to artificially create | a demand by the government requiring it for, say, safety | critical software but I have a feeling companies wouldn't | want this out of their own accord because that license | gives the employee a bit more bargaining power. It also | creates a large risk to the SWEs due to the lack of | codified standards and the inherent difficulty in software | testing. It's not like a mechanical engineer who can | confidently claim a system is safe because it was built to | ASME standards. | 908B64B197 wrote: | > It could make sense to artificially create a demand by | the government requiring it for, say, safety critical | software but I have a feeling companies wouldn't want | this out of their own accord because that license gives | the employee a bit more bargaining power. | | For any software purchase above a certain amount the | government should be forced to have someone with some | kind of license sign on the request. So many projects | have doubled or tripled in price after it was discovered | the initial spec didn't make any sense. | bumby wrote: | Do you mean sign as in qualify that the software is | "good"? | | In general, they already have people who are supposed to | be responsible for those estimates and decisions (project | managers, contracting officers etc.) but whether or not | they're actually held accountable is another matter. | Having a license "might" ensure some modicum of domain | expertise to prevent what you talk about but I have my | doubts | 908B64B197 wrote: | > Do you mean sign as in qualify that the software is | "good"? | | We're not there yet. Just someone to review the final | spec and see if it makes any sense at all. | | Canonical example is the Canadian Phenix Payroll System. | The spec described payroll rules that didn't make any | sense. The project tripled in cost because they had to | rewrite it almost completely. | | > In general, they already have people who are supposed | to be responsible for those estimates and decisions | (project managers, contracting officers etc.) but whether | or not they're actually held accountable is another | matter. | | For other projects, they must have an engineer's | signature else nothing gets built. So someone does the | final sanity check for the project managers-contracting | officers-humanities-diploma bureaucrat. For software, | none of that is required, despite the final bill being | often as expensive as a bridge. | | > Having a license "might" ensure some modicum of domain | expertise to prevent what you talk about but I have my | doubts | | Can't be worse than none at all. | dragonwriter wrote: | > OOP was pitched as a one-size-fits-all solution to all | problems, and as a checklist of items that would turn a | cheap offshored programmer into a real software engineer. | | Not initially. Eventually, everything that reaches a | certain minimal popularity in software development level | gets pitched by snake-oil salesman to enterprise management | as a solution to that problem, including things developed | specifically to deal with the problem of othee solutions | being cargo culted and repackaged that way, whether its a | programming paradigm or a development methodology or | metamethodology. | 0xdeadbeefbabe wrote: | > At some point CS as a profession has to find the right | balance of art and science. | | That seems like such a hard problem. Why not tackle a simpler | one? | bumby wrote: | I didn't downvote but I'll weigh in on why I disagree. | | The glib answer is "because it's worth it." As software | interfaces with more and more of our lives, managing the | risks becomes increasingly important. | | Imagine if I transported you back 150 years to when the | industrial revolution and steam power were just starting to | take hold. At that time there were no consensus standards | about what makes a mechanical system "good"; it was much | more art than science. The numbers of mishaps and the | reliability reflected this. However, as our knowledge grew | we not only learned about what latent risks were posed by, | say, a boiler in your home but we also began to define what | is an acceptable design risk. There's still art involved, | but the science we learned (and continue to learn) provides | the guardrails. The Wild West of design practice is no | longer acceptable due to the risk it incurs. | yowlingcat wrote: | I imagine that's part of why different programming | languages exist -- IE you have slightly less footguns with | Java than with C++. | | The problem is, the nature of writing software | intrinsically requires a balance of art and science no | matter what language it is. That is because solving | business problems is a blend of art and science. | | It's a noble aim to try and avoid solving unnecessarily | hard problems, but when it comes to the customer, a certain | amount of it gets incompressible. So you can't avoid it. | carlmr wrote: | >the practice of deviating from standardized best-practices | should be communicated in terms of the risk rather than | claiming it's just subjective. | | The problem I see with this is that programming could be | described as a kind of general problem solving. Other | engineering disciplines standardize methods that are far more | specific, e.g. how to tighten screws. | | It's hard to come up with specific rules for general problems | though. Algorithms are just solution descriptions in a | language the computer and your colleagues can understand. | | When we look at specific domains, e.g. finance and accounting | software, we see industry standards have already emerged, | like dealing with fixed point numbers instead of floating | point to make calculation errors predictable. | | If we now start codifying general software engineering, I'm | worried we will just codify subjective opinions about general | problem solving. And that will stop any kind of improvement. | | Instead we have to accept that our discipline is different | from the others, and more of a design or craft discipline. | bumby wrote: | > _kind of general problem solving_ | | Could you elaborate on this distinction? At the superficial | level, "general problem solving" is exactly how I describe | engineering in general. The example of tightening screws is | just a specific example of a fastening problem. In that | context, codified standards are an industry consensus on | how to solve a specific problem. Most people wrenching on | their cars are not following ASME torque guidelines but | somebody building a spacecraft should be. It helps define | the distinction of a professional build for a specific | system. Fastening is the "general problem"; fastening | certain materials for certain components in certain | environments is the specific problem that the standards | uniquely address. | | For software, there are quantifiable measures. As an | example, there are some sorting algorithms that are | objectively faster than others. For those systems that it | matters in terms of risk, it probably shouldn't be left up | to the subjective eye of an individual programmer, just | like the spacecraft should rely on a technician's | subjective opinion of that a bolt is "meh, tight enough." | | > _I 'm worried we will just codify subjective opinions | about general problem solving._ | | Ironically, this is the same attitude in many circles of | traditional engineering. People who don't want adhere to | industry standards have their own subjective ideas about | should solve the problem. Standards aren't always right, | but it creates a starting point to 1) identify a risk and | 2) find an acceptable way to mitigate it. | | > _Instead we have to accept that our discipline is | different from the others_ | | I _strongly_ disagree with this and I 've seen this | sentiment used (along with "it's just software") to justify | all kinds of bad design choices. | hinkley wrote: | > The best code is code you don't have to read because of well | named functional boundaries. | | I don't know which is harder. Explaining this about code, or | about tests. | | The people with no sense of DevX see nothing wrong with writing | tests that fail as: Expected undefined to be | "foo" | | If you make me read the tests to modify your code, I'm probably | going to modify the tests. Once I modify the tests, you have no | idea if the new tests still cover all of the same concerns | (especially if you wrote tests like the above). | | Make the test red before you make it green, so you know what | the errors look like. | therealdrag0 wrote: | Oh god. Or just the tests that are walls of text, mixes of | mocks and initializers and constructors and method calls. | | Like good god, extract that boiler plate into a function. Use | comments and white space to break it up and explain the | workflow. | hinkley wrote: | I have a couple people who use a wall of boiler plate to do | something 3 lines of mocks could handle, and not couple the | tests to each other in the process. | | Every time I have to add a feature I end up rewriting the | tests. But you know, code coverage, so yay. | Aeolun wrote: | > Make the test red before you make it green, so you know | what the errors look like. | | Oh! I like this. I never considered this particular reason | why making tests fail first might be a good idea. | danielskogly wrote: | Previous discussion from 11 months ago: | https://news.ycombinator.com/item?id=23671022 | zabzonk wrote: | Martin is, and always has been, a plagiarist, ghost-written, | clueless, idiot, with a way of convincing other know-nothings | that he knew something. At one time he tried to set up a | reputation on StackOverflow, and was rapidly seen off. | | Toxic. Avoid. | lamontcg wrote: | I recently read this cover to cover and left a negative review on | Amazon. I'm happy to see I'm not the only one, and this goes into | it in a whole lot more detail. | | The author seems like they took a set of rules that are good for | breaking beginning programmers bad habits and then applied them | into the extreme. There's a whole lot of rules which aren't bad | up until you try to apply them like laws of gravity that must | always be followed. Breaking up big clunky methods that do way | too much is great for readability, right up until you're spraying | one line helper methods all over your classes and making them | harder to read because now you're inventing your own domain | specific language everyone has to learn (often with the wrong | abstractions which get extended through the years and wind up | needing a massive refactoring down the road which would have been | simpler with fewer methods and abstractions involved at the | start). | | A whole lot of my job is taking classes, un-DRY'ing them | completely so there's duplication all over the place, then | extracting the right (or at least more correct) abstractions to | make the whole thing simple and readable and tight. | SethMurphy wrote: | 'Clean Code' is a style, not all practices are best. I feel that | a good software team understands each other's styles, therefore | making it easier to read others code within the context of a | style. However, when people disagree on code style it has a way | of creating cliques within teams, so sometimes it's just easier | to pick a style that is well documented already and be done with | mainly petty disagreements. Clean code fits the definition of | well documented and is a lazy way of defining a team wide style. | gentleman11 wrote: | > Opinions of SOLID have been steadily lowering in recent years | too | | God help the project that ignores single responsibility, the | guidelines for interfaces, the idea of open closed as an ideal, | and that refuses to ever use dependency inversion. My coding got | 3x easier to scale and write quickly after adding just a few | ideas from solid. But, like the author says repeatedly, advice | from 2008 must be irrelevant today | ziml77 wrote: | I agree. Trying to apply the lessons in there leads to code that | is more difficult to read and reason about. Making it "read like | a book" and keeping functions short sound good on the surface but | they lead to lines getting taken up entirely by function names | and a nightmare of tracking call after call after call. | | It's been years since I've read the book and I'm still having | trouble with the bad ideas from there because they're so well | stuck with me that I feel like I'm doing things wrong if I don't | follow the guidelines in there. Sometimes I'll actually write | something in a sensible way, change it to the Clean Code way, and | then revert it back to where it was when I realize my own code is | confusing me when written like that. | | This isn't just a Robert C Martin issue. It's a cultural issue. | People need to stop shaming others if their code doesn't align | with Clean Code. People need to stop preaching from the book. | ffhhj wrote: | I make my code "read like a book" with a line comment for each | algorithmic step inside a function, and adding line-ending | comments to clarify. So functions are just containers of steps | designed to reduce repetition, increase visibility, and | minimize data passing and globals. | hashkb wrote: | Let's not throw the baby out with the bathwater. We can still | measure how quickly new (average) developers become proficient, | average team velocity over time, and a host of other metrics that | tell us if we are increasing or decreasing the quality of our | code over time. Ignoring it all because it's somewhat subjective | is selfish and bad for your business. | | Leave off the word "clean" or whatever... DO have metrics and | don't ignore them. You have people on your team that make it | easier for the others, and people who take their "wins" at the | expense of their teammates' productivity. | slver wrote: | How about we throw Clean Architecture in this while we're at it. | And also realize that the only rule in SOLID that isn't defined | subjectively or partially is the "L". | dham wrote: | Yes let's please do this. I'm tried of this book being brought up | at work. | | My clean code book: | | * Put logic closest to where it needs to live (feature folders) | | * WET (Write everything twice), figure out the abstraction after | you need something a 3rd time | | * Realize there's no such thing as "clean code" | thebackup wrote: | I read Clean Code in 2010 and trying out and applying some of | the principles really helped to make my code more maintainable. | Now over 10 years later I have come to realize that you cannot | set up too many rules on how to structure and write code. It is | like forcing all authors to apply the same writing style or all | artists to draw their paintings with the exact same technique. | With that analogy in mind, I think that one of the biggest | contributors to messy code is having a lot of developers, all | with different preferences, working in the same code base. Just | imagine having 100 different writers trying to write a book, | this is the challenge we are trying to address. | BurningFrog wrote: | https://en.wikipedia.org/wiki/Rule_of_three_(computer_progra... | 29athrowaway wrote: | > Realize there's no such thing as "clean code" | | You can disagree over what exactly is clean code. But you will | learn to distinguish what dirty code is when you try to | maintain it. | | As a person that has had to maintain dirty code over the years, | hearing someone saying dirty code doesn't exist is really | frustrating. Noone wants to clean up your code, but doing it is | better than allowing the code to become unmaintainable, that's | why people bring up that book. If you do not care about what | clean code is, stop making life difficult for people that do. | rendall wrote: | > _hearing someone saying dirty code doesn 't exist is really | frustrating_ | | Not sure why you're being downvoted, but as an unrelated | aside, the quote you're responding to literally did not say | this. | | > _that 's why people bring up that book_ | | I think the point is that following that book does not really | lead to Clean Code. | 29athrowaway wrote: | > I think the point is that following that book does not | really lead to Clean Code. | | And that is why I started saying "You can disagree over | what exactly is clean code". Different projects have | different requirements. Working on some casual website is | not the same as working on a pacemaker firmware. | rendall wrote: | Yeah, I don't know that we're disagreeing here. For me, | there's definitely good code and bad code. I read OP more | as saying perfect is the enemy of good, not that all code | is good code. | | I'd say if you want good code in your working | environment, set up a process for requiring it. Automated | testing and deployment, and code reviews would be the way | to go. Reading _Clean Code_ won 't get you there. | ljm wrote: | I think it's more that clean code doesn't exist because | there's no objective measure of this (and those services that | claim there are are just as dangerous as Clean Code, the | book); anyone can come along and find something about the | code that could be tidied up. And legacy is legacy, it's a | different problem space to the one a greenfield project | exists in. | | > As a person that has to maintain dirty code | | This is a strange credential to present and then use as a | basis to be offended. Are you saying that you have dirty code | and have to keep it dirty? | 29athrowaway wrote: | The counterintuitive aspect of this problem that acts as a | trap of the less pragmatic people, is that an objective | measure is not always necessary. | | Let's say you are a feeding a dog. You can estimate what | amount of food is dog too little, and what amount of dog | food is too much... but now, some jerk comes around and | tells you they're going to feed the dog the next time. You | agree. | | You check the plate, and there's very little food in it. So | you say: "hey, you should add more dog food". | | Then, the jerk reacts by putting an excessive amount of | food in it, just to fuck with you. Then you say "that's too | much food!"... So then the jerk reacts saying "you should | tell me exactly how many dog pellets I should put on the | plate". | | Have you ever had to count dog pellets individually to feed | a dog? no. You haven't, you have never done it, yet you | have fed dogs for years without problems just using a good | enough estimate of how much a dog can eat. | | Just to please the fucking jerk, you take the approximate | amount dog food you regularly feed the dog every day, count | the pellets, and say: "there, you fuck, 153 dog pellets". | | But the jerk is not happy yet. Just to really fuck with | you, the guy will challenge you and say: "so what happens | if I feed the dog 152 pellets, or 154... see? you are full | of shit". Then you have to explain the jerk that 153 was | never important, what's important is the approximate amount | of dog food. But the jerk doesn't think that way, the jerk | wants a fucking exact number so they can keep fighting | you... | | Then the jerk will probably say that a dog pellet is not a | proper unit of mass, and then the jerk will say that | nutrients are not equally distributed in dog pellets, and | the bullshit will go on and on and on. | | And if you are ever done discussing the optimal number of | pellets then there will be another discussion about the | frequency in which you feed the dog, and you will probably | end up talking about spacetime and atomic clocks and the | NTP protocol and relativity and inertial frames just to | please the jerk whose objective is just to waste your time | until you give up trying to enforce good practices. | | And this is how the anti-maintainability jerks operate, by | getting into endless debates about how an objective measure | of things is required, when in reality, it's not fucking | needed and it never was. Estimation is fine. | | Just like you won't feed a dog a fucking barrel of dog food | you won't create a function that is 5 thousand lines of | code long because it's equally nonsensical. | | So in the end, what do you do? you say: this many lines of | code in a function is too fucking much, don't write | functions this long. Why? because I say so, end of debate, | fuck it. | | There's a formal concept for this in philosophy, but that's | up to you to figure out. | Twisol wrote: | > There's a formal concept for this in philosophy, but | that's up to you to figure out. | | I think I know this one! Is it the paradox of the heap? | | https://en.wikipedia.org/wiki/Sorites_paradox | 29athrowaway wrote: | Exactly. | Chris2048 wrote: | Another way to say (I think) the same is: | | The cost of analysis outweighs the benefit. | | In this case the cost of counting food pellets versus the | benefit of precision; but in software generally it is the | high cost of (accurate) estimating versus the benefit of | just getting on with it. | | It is also, to some degree, the impossibility of | estimating given that the task is given to someone who is | an expert at writing code, not estimating. Coding | expertise give insight into how long coding tasks take, | but that it the least critical component of estimating a | task. Writing code and seeing if it works is the best | estimate; sometimes the prototype works first time, never | always. | | best example of this: https://xkcd.com/1445/ | ljm wrote: | > Just to please the fucking jerk | | That's probably mistake number one right there. | | There's another formal concept for this in philosophy, | related to wrestling with pigs. | 29athrowaway wrote: | Just trying to illustrate why it does not make sense to | even get started down that path and the conclusion | reflects this. | ljm wrote: | I agree. | | Despite approaching this from completely different angles | I think we're roughly pointing in the same direction. | | A pragmatist would allow for a strong baseline of good | practices and automated rules, with some room for | discretion where it counts. That way, no one gets bogged | down in exhausting debates, and people who work better | with strict constraints can avoid ambiguity, but you can | still bend the rules with some justification. I don't | like all of the rules, but it's easy to fall back on the | tooling than it is to hash out another method length | debate. | | As far as maintaining code goes, I've had the | (mis)fortune of dealing with overly abstracted messes and | un-architected spaghetti. I'm not sure which is worse, | but what I rarely have to deal with now is all the crap | that is auto-formatted away. | | If I'm involved in any debate, it's around architecture | and figuring out how to write better code by properly | understanding what we're trying to achieve, rather than | trying to lay down Clean Code style design patterns as if | they were Rubocop rules. | 29athrowaway wrote: | Well, if there is a problem, you can identify it, discuss | it and address it, instead of ignoring it entirely which | is what some people do. Do what works for your team. | | Excessive abstractions, leaky abstractions and such are | indeed a problem. The principle or least power works | there (given a choice of solutions, use the least | flexible/powerful to solve your problem). | TeMPOraL wrote: | > _Put logic closest to where it needs to live (feature | folders)_ | | Can you say more about this? | | I _think_ I may have stumbled on a similar insight myself. In a | side project (a roguelike game), I 've been experimenting with | a design that treats features as first-class, composable design | units. Here is a list of the subfolder called game-features in | the source tree: actions collision | control death destructibility game- | feature.lisp hearing kinematics log | lore package.lisp rendering sight | simulation transform | | An extract from the docstring of the entire game-feature | package: "A Game Feature is responsible for | providing components, events, event handlers, queries and | other utilities implementing a given aspect of the game. | It's primarily a organization tool for gameplay code. | Each individual Game Feature is represented by a class | inheriting from `SAAT/GF:GAME-FEATURE'. To make use of a | Game Feature, an object of such class should be created, | preferably in a system description (see `SAAT/DI'). | This way, all rules of the game are determined by a collection | of Game Features loaded in a given game. | Game Features may depend on other Game Features; this is | represented through dependencies of their classes." | | The project is still very much work-in-progress | (procrastinating on HN doesn't leave me much time to work on | it), and most of the above features are nowhere near | completion, but I found the design to be mostly sound. Each | game feature provides code that implements its own concerns, | and exports various functions and data structures for other | game features to use. This is an inversion of traditional | design, and is more similar to the ECS pattern, except I bucket | _all_ conceptually related things in one place. ECS Components | and Systems, utility code, event definitions, etc. that | implement a single conceptual game aspect live in the same | folder. Inter-feature dependencies are made explicit, and game | "superstructure" is designed to allow GFs to wire themselves | into appropriate places in the event loop, datastore, etc. - so | in game startup code, I just declare which features I want to | have enabled. | | (Each feature also gets its set of integration tests that use | synthetic scenarios to verify a particular aspect of the game | works as I want it to.) | | One negative side effect of this design is that the execution | order of handlers for any given event is hard to determine from | code. That's because, to have game features easily compose, GFs | can request particular ordering themselves (e.g. "death" can | demand its event handler to be executed after "destructibility" | but before "log") - so at startup, I get an ordering preference | graph that I reconcile and linearize (via topological sorting). | I work around this and related issues by adding debug utilities | - e.g. some extra code that can, after game startup, generate a | PlantUML/GraphViz picture of all events, event handlers, and | their ordering. | | (I apologize for a long comment, it's a bit of work I always | wanted to talk about with someone, but never got around to. The | source of the game isn't public right now because I'm afraid of | airing my hot garbage code.) | Chris2048 wrote: | I'd be interested in how you attempt this. Is it all in lisp? | | It might be hard to integrate related things, e.g. physical | simulation/kinematics <- related to collisions, and maybe | sight/hearing <- related to rendering; Which is all great if | information flows one way, as a tree, but maybe complicated | if it's a graph with intercommunication. | | I thought about this before, and figured maybe the design | could be initially very loose (and inefficient), but then a | constraint-solver could wire things up as needed, i.e. pre- | calculate concerns/dependencies. | | Another idea, since you mention "logs" as a GF: AOP - using " | join points" to declaratively annotate code. This better | handles code that is less of a "module" (appropriate for | functions and libraries) and more of a cross-cutting "aspect" | like logging. This can also get hairy though: could you | treated "(bad-path) exception handling" as an aspect? what | about "security"? | TeMPOraL wrote: | > _I 'd be interested in how you attempt this. Is it all in | lisp?_ | | Yes, it's all in Common Lisp. | | > _It might be hard to integrate related things, e.g. | physical simulation /kinematics <- related to collisions, | and maybe sight/hearing <- related to rendering;_ | | It is, and I'm cheating a bit here. One simplification is | that I'm writing a primarily text-based roguelike, so I | don't have to bother with a lot of issues common to real- | time 3D games. I can pick and choose the level of details I | want to go (e.g. whether to handle collisions at a tile | granularity, or to introduce sub-tile coordinates and maybe | even some kind of object shape representation). | | > _Which is all great if information flows one way, as a | tree, but maybe complicated if it 's a graph with | intercommunication._ | | The overall simulation architecture I'm exploring in this | project is strongly event-based. The "game frame" is | basically pulling events from a queue and executing them | until the queue is empty, at which point the frame is | considered concluded, and simulation time advances forward. | It doesn't use a fixed time step - instead, when a | simulation frame starts, the code looks through "actions" | scheduled for game "actors" to find the one that will | complete soonest, and moves the simulation clock to the | completion time of that action. Then the action completion | handler fires, which is the proper start of frame - | completion handler will queue some events, and handlers of | those events will queue those events, and the code just | keeps processing events until the queue empties again, | completing the simulation frame. | | Structure-wise, simulation GF defines the concept of "start | frame" and "end frame" (as events), "game clock" (as query) | and ability to shift it (as event handler), but it's the | actions GF that contains the computation of next action | time. So, simulation GF knows how to tell and move time, | but actions GF tells it where to move it to. | | This is all supported by an overcomplicated event loop that | lets GFs provide hints for handler ordering, but also | separates each event handling process into four chains: | pre-commit, commit, post-commit and abort. Pre-commit | handlers fire first, filling event structure with data and | performing validation. Then, commit handlers apply direct | consequences of event to the real world - they alter the | gameplay state. Then, post-commit handlers process further | consequences of an event "actually happening". | Alternatively, abort handlers process situations when an | event was rejected during earlier chains. All of them can | enqueue further events to be processed this frame. | | So, for example, when you fire a gun, pre-commit handlers | will ensure you're able to do it, and reject the event if | you can't. If the event is rejected, abort chain will | handle informing you that you failed to fire. Otherwise, | the commit handlers will instantiate an appropriate | projectile. Post-commit handlers may spawn events related | to the weapon being fired, such as informing nearby enemies | about the discharge. | | This means that e.g. if I want to implement "ammunition" | feature, I can make an appropriate GF that attaches a pre- | commit handler to fire event - checking if you have bullets | left and rejecting the event if you don't (events rejected | in pre-commit stage are considered to "have never | happened"), and a post-commit handler on the same event to | decrease your ammo count. The GF is also responsible for | defining appropriate components that store ammo count, so | that (in classical ECS style) your "gun" entity can use it | to keep track of ammunition. It also provides code for | querying the current count, for other GFs that may care | about it for some reason (and the UI rendering code). | | > _I thought about this before, and figured maybe the | design could be initially very loose (and inefficient), but | then a constraint-solver could wire things up as needed, | i.e. pre-calculate concerns /dependencies._ | | I'm halfway there and I could easily do this, but for now | opted against it, on the "premature optimization" grounds. | That is, since all event handlers are registered when the | actual game starts, I "resolve constraints" (read: convert | sorting preferences into a DAG and toposort it; it's dumb | and very much incorrect, but works well enough for now) and | linearize handlers - so that during gameplay, each event | handler chain (e.g. "fire weapon", pre-commit) is just a | sequence of callbacks executed in order. It would be | trivial to take such sequence, generate a function that | executes them one by one (with the loop unrolled), and | compile it down to metal - Common Lisp lets you do stuff | like that - but I don't need it right now. | | > _Another idea, since you mention "logs" as a GF_ | | FWIW, logs GF is implementing the user-facing logs typical | for roguelike games - i.e. the bit that says "You open the | big steel doors". Diagnostic logs I do classically. | | > _AOP - using " join points" to declaratively annotate | code_ | | In a way, my weird multi-chain event loop _is_ a | reimplementation of AOP. Method combinations in Common Lisp | are conceptually similar too, but I 'm not making big use | of them in game feature-related code. | | > _This can also get hairy though: could you treated "(bad- | path) exception handling" as an aspect? what about | "security"?_ | | Yeah, I'm not sure if this pattern would work for these - | particularly in full-AOP, "inject anything anywhere" mode. | I haven't tried it. Perhaps, with adequate tooling support, | it's workable? Common Lisp is definitely not a language to | try this in, though - it's too dynamic, so tooling would | not be able to reliably tell you about arbitrary pointcuts. | | In my case, I restricted the "feature-oriented design" to | just _game features_ - I feel it has a chance of working | out, because in my mind, quite a lot of gameplay mechanics | are conceptually additive. This project is my attempt at | experimentally verifying if one could actually make a | working game this way. | mike_ivanov wrote: | Sounds interesting. Is the game open source? Published | anywhere? | TeMPOraL wrote: | Not at this moment. I intend to publish it under GPLv3, | after I get the MVP done. | lxdesk wrote: | I've gone down roads similar to this. Long story short - the | architecture solves for a lower priority class of problem, | w/r to games, so it doesn't pay a great dividend, and you add | a combination of boilerplate and dynamism that slows down | development. | | Your top issue in the runtime game loop is always with | concurrency and synchronization logic - e.g. A spawns before | B, if A's hitbox overlaps with B, is the first frame that a | collision event occurs the frame of spawning or one frame | after? That's the kind of issue that is hard to catch, occurs | not often, and often has some kind of catastrophic impact if | handled wrongly. But the actual effect of the event is | usually a one-liner like "set a stun timer" - there is | nothing to test with respect to the event itself! The | perceived behavior is intimately coupled to when its | processing occurs and when the effects are "felt" elsewhere | in the loop - everything's tied to some kind of clock, | whether it's the CPU clock, the rendered frame, turn-taking, | or an abstracted timer. These kinds of bugs are a matter of | bad specification, rather than bad implementation, so they | resist automated testing mightily. | | The most straightforward solution is, failing pure functions, | to write more inline code(there is a John Carmack posting on | inline code that I often use as a reference point). Enforce a | static order of events as often as possible. Then debugging | is always a matter of "does A happen before B?" It's there in | the source code, and you don't need tooling to spot the | issue. | | The other part of this is, how do you load and initialize the | scene? And that's a data problem that does call for more | complex dependency management - but again, most games will | aim to solve it statically in the build process of the game's | assets, and reduce the amount of game state being serialized | to save games, reducing the complexity surface of everything | related to saves(versioning, corruption, etc). With a | roguelike there is more of an impetus to build a lot of | dynamic assets(dungeon maps, item placements etc.) which | leads to a larger serialization footprint. But ultimately the | focus of all of this is on getting the data to a place where | you can bring it back up and run queries on it, and that's | the kind of thing where you could theoretically use SQLite | and have a very flexible runtime data model with a robust | query system - but fully exploiting it wouldn't have the | level of performance that's expected for a game. | | Now, where can your system make sense? Where the game loop is | actually dynamic in its function - i.e. modding APIs. But | this tends to be a thing you approach gradually and | grudgingly, because modders aren't any better at solving | concurrency bugs and they are less incentivized to play nice | with other mods, so they will always default to hacking in | something that stomps the state, creating intermittent race | conditions. So in practice you are likely to just have | specific feature points where an API can exist(e.g. add a new | "on hit" behavior that conditionally changes the one-liner), | and those might impose some generalized concurrency logic. | | The other thing that might help is to have a language that | actually understands that you want to do this decoupling and | has the tooling built in to do constraint logic programming | and enforce the "musts" and "cannots" at source level. I | don't know of a language that really addresses this well for | the use case of game loops - it entails having a whole | general-purpose language already and then also this other | feature. Big project. | | I've been taking the approach instead of aiming to develop | "little languages" that compose well for certain kinds of | features - e.g. instead of programming a finite state machine | by hand for each type of NPC, devise a subcategory of state | machines that I could describe as a one-liner, with chunks of | fixed-function behavior and a bit of programmability. Instead | of a universal graphics system, have various programmable | painter systems that can manipulate cursors or selections to | describe an image. The concurrency stays mostly static, but | the little languages drive the dynamic behavior, and because | they are small, they are easy to provide some tooling for. | TeMPOraL wrote: | Thanks for the detailed evaluation. I'll start by | reiterating that the project is a typical tile-based | roguelike, so some of the concerns you mention in the | second paragraph don't apply. Everything runs sequentially | and deterministically - though the actual order of | execution may not be apparent from the code itself. I | mitigate it to an extent by adding introspection features, | like e.g. code that dumps PlantUML graphs showing the | actual order of execution of event handlers, or their | relationship with events (e.g. which handlers can send what | subsequent events). | | I'll also add that this is an experimental hobby project, | used to explore various programming techniques and | architecture ideas, so I don't care about most constraints | under which commercial game studios operate. | | > _The perceived behavior is intimately coupled to when its | processing occurs and when the effects are "felt" elsewhere | in the loop - everything's tied to some kind of clock, | whether it's the CPU clock, the rendered frame, turn- | taking, or an abstracted timer. These kinds of bugs are a | matter of bad specification, rather than bad | implementation, so they resist automated testing mightily._ | | Since day one of the project, the core feature was to be | able to run headless automated gameplay tests. That is, | input and output are isolated by design. Every "game | feature" (GF) I develop comes with automated tests; each | such test starts up a minimal game core with fake (or null) | input and output, the GF under test, and all GFs on which | it depends, and then executes faked scenarios. So far, at | least for minor things, it works out OK. I expect I might | hit a wall when there are enough interacting GFs that I | won't be able to correctly map desired scenarios to actual | event execution orders. We'll see what happens when I reach | that point. | | > _that 's the kind of thing where you could theoretically | use SQLite and have a very flexible runtime data model with | a robust query system - but fully exploiting it wouldn't | have the level of performance that's expected for a game._ | | Funny you should mention that. | | The _other_ big weird thing about this project is that it | uses SQLite for runtime game state. That is, entities are | database rows, components are database tables, and the | canonical gameplay state at any given point is stored in an | in-memory SQLite database. This makes saving /loading a | non-issue - I just use SQLite's Backup API to dump the game | state to disk, and then read it back. | | Performance-wise, I tested this approach extensively up | front, by timing artificial reads and writes in expected | patterns, including simulating a situation in which I pull | map and entities data in a given range to render them on | screen. SQLite turned out to be much faster than I | expected. On my machine, I could easily get 60FPS out of | that with minimum optimization work - but it did consume | most of the frame time. Given that I'm writing a ASCII- | style, turn(ish) roguelike, I don't actually need to query | all that data 60 times per second, so this is quite | acceptable performance - but I wouldn't try that with a | real-time game. | | > _The other thing that might help is to have a language | that actually understands that you want to do this | decoupling and has the tooling built in to do constraint | logic programming and enforce the "musts" and "cannots" at | source level. I don't know of a language that really | addresses this well for the use case of game loops - it | entails having a whole general-purpose language already and | then also this other feature. Big project._ | | Or a Lisp project. While I currently do constraint | resolution at runtime, it's not hard to move it to compile | time. I just didn't bother with it yet. Nice thing about | Common Lisp is that the distinction between | "compilation/loading" and "runtime" is somewhat arbitrary - | any code I can execute in the latter, I can execute in the | former. If I have a function that resolves constraints on | some data structure and returns a sequence, and that data | structure can be completely known at compile time, it's | trivial to have the function execute during compilation | instead. | | > _I 've been taking the approach instead of aiming to | develop "little languages" that compose well for certain | kinds of features_ | | I'm interested in learning more about the languages you | developed - e.g. how your FSMs are encoded, and what that | "programmable painter system" looks like. In my project, I | do little languages too (in fact, the aforementioned "game | features" are a DSL themselves) - Lisp makes it very easy | to _just_ create new DSLs on the fly, and to some extent | they inherit the tooling used to power the "host" | language. | bosswipe wrote: | Your name WET is kind of funny but it is usually refered to as | the rule of 3, | https://en.m.wikipedia.org/wiki/Rule_of_three_(computer_prog... | innagadadavida wrote: | How do you deal with other colleagues that have all the energy | and time to push for these practices and I feel makes things | worse than the current state? | Spivak wrote: | Explain that the wrong abstraction makes code more | complicated than copy-paste and that before you can start | factoring out common code you need to be sure the | relationship is fundamental and not coincidental. | bluGill wrote: | And get ignored or laughed at. Some people know the rules | and nothing but. | throw1234651234 wrote: | WET is great until JSON token parsing breaks and a junior dev | fixes it in one place and then I am fixing the same exact | problem somewhere else and moving it into a shared file. If | it's the exact same functionality, move it into a | service/helper. | Cthulhu_ wrote: | This is what I'm doing even while creating new code. There's a | few instances for example where the "execution" is down to a | single argument - one of "activate", "reactivate" and | "deactivate". But I've made them into three distinct, separate | code paths so that I can work error and feedback messages into | everything without adding complexity via arguments. | | I mean yes it's more verbose, BUT it's also super clear and | obvious what things do, and they do not leak the underlying | implementation. | KaiserPro wrote: | repeat after me: | | Document. | | Your. | | Shit. | | everything else can do one. Just fucking write documentation as | if you're the poor bastard trying to maintain this code with no | context or time. | caymanjim wrote: | Documentation is rarely adequately maintained, and nothing | enforces that it stay accurate and maintained. | | Comments in code can lie (they're not functional); can be | misplaced (in most languages, they're not attached to the | code they document in any enforced way); are most-frequently | used to describe things that wouldn't require documenting if | they were just named properly; are often little more than | noise. Code comments should be exceedingly-rare, and only | used to describe exception situations or logic that can't be | made more clear through the use of better identifiers or | better-composed functions. | | External documentation is usually out-of-sight, out-of-mind. | Over time, it diverges from reality, to the point that it's | usually misleading or wrong. It's not visible in the code | (and this isn't an argument in favor of in-code comments). | Maintaining it is a burden. There's no agreed-upon standard | for how to present or navigate it. | | The best way to document things is to name identifiers well, | write functions that are well-composed and small enough to | understand, stick to single-responsibility principles. | | API documentation is important and valuable, especially when | your IDE can provide it readily at the point of use. Whenever | possible, it should be part of the source code in a formal | way, using annotations or other mechanisms tied to the code | it describes. I wish more languages would formally include | annotation mechanisms for this specific use case. | KaiserPro wrote: | > Documentation is rarely adequately maintained, | | yes, and the solution is to actually document. | | > wouldn't require documenting if they were just named | properly | | I mean not really. Having decent names for things tells us | what you are doing, but not why. | | > only used to describe exception situations | | Again, just imagine if that wasn't the case. Imagine people | had the empathy to actually do a professional job? | | > The best way to document things is to name identifiers | well, write functions that are well-composed and small | enough to understand, stick to single-responsibility | principles. | | No, thats the best way to code. | | The best way to document is to imagine you are a new | developer to the code base, and any information they should | know is where they need it. Like your code, you need to | test your documentation. | | I know you don't _like_ documenting, but thats not the | point. Its about being professional, just imagine if an | engineer didn't _like_ doing certification of their stuff? | they'd loose their license. Sure you could work it out | later, but thats not the point. You are paid to be | professional, not a magician. | [deleted] | caymanjim wrote: | > I know you don't _like_ documenting, but thats not the | point. | | On the contrary, I _like_ writing documentation. What I | can 't stand is unnecessary documentation; documentation | of obvious things; documentation as a replacement for | self-documenting code; documentation for a vague ill- | defined future use-case that probably won't exist | (YAGNI); and many of the other documentation failures | I've already mentioned. It has nothing to do with my | professionalism. It has to do with wasting time and | energy and using it as a crutch in place of more valuable | knowledge transfer mechanisms. | Oddskar wrote: | Self-documenting code is a complete fantasy for | everything but completely trivial logic. | bengale wrote: | I've never heard the term WET before but that's exactly what I | do. | | The other key thing I think is not to over-engineer | abstractions you don't need yet. But to try and leave 'seams' | where it's obvious how to tease code about _if_ you need to | start building abstractions. | UglyToad wrote: | I think this ties in to something I've been thinking, though it | might be project specific. | | Good code should be written to be easy to delete. | | 'Clever' abstractions work against this. We should be less | precious about our code and realise it will probably need to | change beyond all recognition multiple times. Code should do | things simply so the consequences of deleting it are | immediately obvious. I think your recommendations fit with | this. | msluyter wrote: | Aligns with my current meta-principle, which is that good | code is malleable (easily modified, which includes deletion). | A lot of design principles simply describe this principle | from different angles. Readable code is easy to modify | because you can understand it. Terse code is more easily | modified because there's less of it (unless you've sacrificed | readability for terseness). SRP limits the scope of changes | and thus enhances modifiability. Code with tests is easier to | modify because you can refactor with less fear. Immutability | makes code easier to modify because you don't have to worry | about state changes affecting disparate parts of the program. | | Etc... etc... | | (Not saying that this is the _only_ quality of good code or | that you won't have to trade some of the above for | performance or whatnot at times). | Benjammer wrote: | >code should be written to be easy to delete | | This article tends to make the rounds on here every once in a | while: | https://programmingisterrible.com/post/139222674273/how- | to-w... | munificent wrote: | The unpleasant implication of this is that code has a | tendency towards becoming worse over time. Because the code | that is good enough to be easy to delete or change is, and | the code that is too bad to be touched remains. | jasonwatkinspdx wrote: | One of John Carmack's maxims is "Fight code entropy." As | simple as that is it carries a lot of meaning imo. | anonyfox wrote: | > WET (Write everything twice), figure out the abstraction | after you need something a 3rd time | | so much this. it is _much_ easier to refactor copy pasta code, | than to entangle a mess of "clean code abstractions" for things | that isn't even needed _once_. Premature Abstraction is the | biggest problem in my eyes. | | Write Code. Mostly functions. Not too much. | beckingz wrote: | "Write Code. Mostly Functions. Not too much" made my day and | is excellent advice. | haspok wrote: | > Write Code. Mostly functions. Not too much. | | Think about data structures (types) first. Mostly immutable | structures. Then add your functions working on those | structures. Not too many. | throwaway2037 wrote: | OMG. This is exactly my experience after trying to write | code first for 10+ years. (Yes, I am a terrible [car] | driver, and a totally average programmer!) | | "Bad programmers worry about the code. Good programmers | worry about data structures and their relationships." - | Linus Torvalds | | He wasn't kidding! | | And the bit about "immutable structures". I doubted for | infinity-number-of-years ("oh, waste of memory/malloc!"). | Then suddenly your code needs to be multi-threaded. Now, | immutable structures looks genius! | pjc50 wrote: | > it is _much_ easier to refactor copy pasta code | | So long as it remains identical. Refactoring _almost_ | identical code requires lots of extremely detailed staring to | determine whether or not two things are subtly different. | Especially if you don 't have good test coverage to start | with. | tuatoru wrote: | > requires lots of extremely detailed staring | | Yeah, nah. There are diffing tools. | cerved wrote: | problem is you only diff if things look different | macNchz wrote: | I personally love playing the game called "reconcile these | very-important-but-utterly-without-tests sequences of | gnarly regexes that were years ago copy-pasted in seven | places and since then refactored, but only in three of the | seven places, and in separate efforts each time". | allo37 wrote: | I think where DRY trips people up is when you have what I | call "incidental repetition". Basically, two bits of logic | seem to do exactly the same thing, but the contexts are | slightly different. So you make a nice abstraction that works | well until you need to modify the functionality in one | context and not the other... | bradstewart wrote: | So much this. Especially early in project's life when you | aren't sure what the contexts/data model/etc really need to | be, so much logic looks the same. It becomes so hard to | untangle later. | fiddlerwoaroof wrote: | If you mostly deduplicate by writing functions, fixing this | problem is never very hard: duplicate the function, rename | it and change the call-site. | | The interesting thing about DRY is that opinions about it | seem to depend on what project you've worked on most | recently: I inherited a codebase written by people | skeptical of DRY, and we had a lot of bugs that resulted | from "essential duplication". Other people inherit code | written by "architecture astronauts", and assume that the | grass is greener on the WET side of the fence. | | Personally, having been in both situations, I'd almost | always prefer to untangle a bad abstraction rather than | maintain a WET codebase. | jakelazaroff wrote: | Conversely, fixing duplication is never hard. Just move | the duplicated code into a function. Going in reverse can | be much tougher if the function has become an | abstraction, where you have to figure out what path each | function call was actually taking. | | Or, put another way: I'd much rather deal with | duplication than with coupling problems. | fiddlerwoaroof wrote: | The issue I have is that duplication is a coupling | problem, but there's no evidence in the coupled parts of | the code that the coupling exists. It can be ok on a | single-developer project or if confined to a single file, | but my experience is that it's a major contributor to | unreliability, especially when all the original authors | of the code have left. | munificent wrote: | _> Conversely, fixing duplication is never hard. Just | move the duplicated code into a function._ | | I think the single biggest factor determining the | difficulty of a code change is the size of the codebase. | Codebases with a lot of duplication are larger, and the | scale itself makes them harder to deal with. You may not | even realize when duplication exists because it may be | spread throughout the code. It may be difficult to tell | which code is a duplicate, which is different for | arbitrary reasons, and which is different in subtle but | important ways. | | Once you get to a huge sprawl of code that has a ton of | mostly-pointless repetition, it is a _nightmare_ to tame | it. I would much rather be handed a smaller but more | intertwined codebase that only says something once. | bluGill wrote: | The problem with duplication it is hard to spot and fix. | Converting liters to ml or quarts isn't hard, but the | factors are different, and there is also other units. If | you only do a few of these isn't a big deal, but if you | suddenly realize that you have tons of different | conversions scattered around and you really need to | implement a good unit conversion system it will be really | hard to retrofit everything. Note that even if you have a | literToml, Literto Quart and MileToKm functions | retrofitting the right system will be hard. (Where I work | we have gone through 4 different designs of a uber unit | system module before we actually got all the requirements | right, and each transition was a major problem) | jeremysalwen wrote: | I think the opposite is true. Bad abstractions can be | automatically removed with copy/paste and constant | propagation. N pieces of code that are mostly the same | but have subtle differences have no automatic way to | combine them into a single function. | 0xffff2 wrote: | N pieces of code that are mostly the same but have subtle | differences _isn 't repetition_ and probably shouldn't be | combined into a single function, especially if the | process of doing so it non-obvious. | bluGill wrote: | Often it is. Earlier in this thread I used the example of | a unit system - one example of there there can be a ton | of repetition to remove, but there are fundamental | differences between liters and meters that make removing | the duplication hard if you didn't realize upfront you | had a problem. Once you get it right converting meters to | chains isn't that hard (I wonder how many reading even | know chain was a unit of measure - much less have any | idea how big it is), but there are a ton of choices to | make it work. | jakelazaroff wrote: | I think they mean if the code does the same thing but has | syntax differences. Variables are named differently, one | uses a for loop while the other uses functional list | operations, etc. | ummonk wrote: | This, exactly this | SassyGrapefruit wrote: | Yep. Re-use is difficult. When you overdo it you cause a | whole new set of problems. I once watched a person write a | python decorator that tried unify HTTP header based caching | with ad hoc application caching done using memcached. | | When I asked exactly what they were trying to accomplish | they kept saying "they didn't want caching in two places". | I think anyone with experience can see that these items are | unrelated beyond both having the word "cache" | | This is what was actually hanging them up ... the language. | Particularly the word "cache". I've seen this trap walked | into over and over. Where a person tries to "unify" logic | simply because two sets of logic contain some of the same | words. | watwut wrote: | Then you copy function and modify one place? I don't get | what is so hard about it. The IDE will even tell you about | all places where function is called, there is no way to | miss one. | dragonwriter wrote: | > it is _much_ easier to refactor copy pasta code, | | Its easy to refactor if its nondivergent copypasta and you do | it everywhere it is used not later than the third iteration. | | If the refactoring gets delayed, the code diverges because | different bugs are noticed and fixed (or thr same bug is | noticed and fixed different ways) in different iterations, | and there are dozens of instances across the code base | (possibly in different projects because it was copypastad | across projects rather than refactored into a reusable | library), the code has in many cases gotten intermixed with | code addressing other concerns... | cerved wrote: | There's a problem with being overly zealous. It's entirely | possible to write bad code, either being overly dry or copy | paste galore. I think we are prone to these zealous rules | because they are concrete. We want an "objective" measure to | judge whether something is good or not. | | DRY and WET are terms often used as objective measures of | implementations, but that doesn't mean that they are rock | solid foundations. What does it mean for something to be | "repeated"? Without claiming to have TheGreatAnswer(tm), some | things come to mind. | | Chaining methods can be very expressive, easy to follow and | maintain. They also lead to a lot of repetition. In an effort | to be "DRY", some might embark on a misguided effort to | combine them. Maybe start replacing `map(x => | x).reduce(y, z => v)` | | with `mapReduce(x => x, (y,z) => v)` | | This would be a bad idea, also known as Suck(tm). | | But there may equally be situations where consolidation makes | sense. For example, if we're in an ORM helper class and we're | always querying the database for an object like so | `objectContext.Orders.Select(e => e.id = y).Include(e => | e.Customers).Include(e => e.Bills).Include(e => | e.AwesomeDogs)...` | | then it with make sense to consolidate that into | `orderIncludingCustomersBillsAndDogs(id) => ...` | | My $0.02: | | Don't needlessly copy-pastes that which is abstractable. | | Don't over abstract at the cost of simplicity and | flexibility. | | Don't be a zealot. | nodoodles wrote: | > Write Code. Mostly functions. Not too much. | | Just wanted to appreciate the nod to good nutritional advice | here ("Eat food. Mostly plants. Not too much"), well done | latk wrote: | Probably a call-back to this post that made the rounds on | HN a few months ago: https://www.brandons.me/blog/write- | code-not-too-much-mostly-... | macNchz wrote: | >it is _much_ easier to refactor copy pasta code | | I totally agree assuming that there will be time to get to | the second pass of the "write everything twice" | approach...some of my least favorite refactoring work has | been on older code that was liberally copy-pasted by well- | intentioned developers expecting a chance to come back | through later but who never get the chance. All too often the | winds of corporate decision making will change and send | attention elsewhere at the wrong moment, and all those copy | pasted bits will slowly but surely drift apart as unfamiliar | new developers come through making small tweaks. | _huayra_ wrote: | I worked on a small team with a very "code bro" culture. No | toxic, but definitely making non-PC jokes. We would often say | "Ask your doctor about Premature Abstractuation" or "Bad | news, dr. says this code has abstractual dysfunction" in code | reviews when someone would build an | AbstractFactoryFactoryTemplateConstructor for a one-off item. | | When we got absorbed by a larger team and were going to have | to "merge" our code review / git history into a larger org's | repos, we learned that a sister team had gotten in big | trouble with the language cops in HR when they discovered | similar language in their git commit history. This brings | back memories of my team panicked over trying to rewrite a | huge amount of git history and code review stuff to sanitize | our language before we were caught too. | morbicer wrote: | Wait whaat? I am not from USA and hail from a much more | blunt culture, what's wrong with those 2 statements? | | To me they're harmless jokes but maybe someone will point | out they're ableist? | jasonwatkinspdx wrote: | Those aren't particularly bad, but dick jokes have no | place in a professional workplace imo. I'm so very weary | of it. And yes, in the past I was part of the problem. | Then I grew up. | _huayra_ wrote: | It wasn't just these phrases, but we took them out to be | safe (thinking that them being jokes about male... | "performance", like from pharma ads on TV with an old man | throwing a football through a tire swing). | | The biggest thing is that we used non-PC language like | "retarded" very casually, not to mention casually | swearing in commit messages e.g. "un-fucking up my prior | commit". Our sister team got in trouble for "swears in | the git commit history", so we wanted to get ahead of | that if possible. | | In a healthy company culture, we'd just say "okay we'll | stop using these terms", but the effort was made to erase | their existence because this was a company where non- | engineering people (e.g. how well managers and HR liked | you) was a big factor in getting promoted. | | Once I realized how messed up that whole situation was, I | left as fast as I could. | icedchai wrote: | If you were "caught", what would happen? You probably have | a couple of zoom calls and get forced to watch sensitivity | training videos. Who cares. | golergka wrote: | > * WET (Write everything twice), figure out the abstraction | after you need something a 3rd time | | There are two opposite situations. One is when several things | are viewed as one thing while they're actually different (too | much abstraction), and another, where a thing is viewed as | different things, when it's actually a single one (when code is | just copied over). | | In my experience, the best way to solve this is to better | analyse and understand the requirements. Do these two pieces of | code look the same because they actually mean thing in the | meaning of the product? Or they just happen to look the same at | this particular moment in time, and can continue to develop in | completely different directions as the product grows? | whimsicalism wrote: | Solving the former is generally way uglier/more obnoxious IMO | than solving the latter, esp. if you were not the person who | designed the former. | thefourthchime wrote: | Ah hahha. I love WET. I always say the only way to write | something correctly is to re-write it. | HWR_14 wrote: | That's not what WET means. The GP is saying you shouldn't | isolate logic in a function until you've cut-and-pasted the | logic in at least two places and plan to do so in a third. | amelius wrote: | > WET (Write everything twice) | | In practice (time pressure) you might end up duplicating it | many times, at which point it becomes difficult to refactor. | Frost1x wrote: | If it's really abstractable it shouldn't be difficult to | refactor. It should literally be a substitution. If it's not, | then you have varied cases that you'd have to go back and | tinker with the abstraction to support. | | It's a similar design and planning principle to building | sidewalks. You have buildings but you don't know exactly the | best paths between everything and how to correctly path | things out. You can come up with your own design but people | will end up ignoring them if they don't fit their needs. | Ultimately, you put some obvious direct connection side walks | and then wait to see the paths people take. You've now | established where you need connections and how they need to | be formed. | | I do a lot of prototyping work and if I had to sit down and | think out a clean abstraction everytime I wanted to get for a | functional prototype, I'd never have a functional prototype-- | plus I'd waste a lot of cognitive capacity on an abstraction | instead of solving the problem my code is addressing. It's | best, from my experience, to save that time and write messy | code but tuck in budget to refactor later (the key is you | have to actually refactor later not just say you will). | | Once you've built your prototype, iterated on it several | times had people break designs forcing hacked out solutions, | and now have something you don't touch often, you usually | know what most the product/service needs to look like. You | then abstract that out and get 80-90% of what you need if | there's real demand. | | The expanded features beyond that can be costly if they | require significant redesign but at that point, you hopefully | have a stable enough product it can warrant continued | investment to refactor. If it doesn't, you saved yourself a | lot of time and energy worrying trying to create a good | abstract design that tends to fail multiple times at early | stages. There's a balance point of knowing when to build | technical debt, when to pay it off, and when to nullify it. | | Again, the critical trick is you have to actually pay off the | tech debt if that time comes. The product investor can't look | bright eyed and linearly extrapolate progress so far thinking | they saved a boatload of capital, they have to understand | shortcuts were taken and the rationale was to fix them if | serious money came along or chuck them in the bin if not. | xpe wrote: | > If it's really abstractable it shouldn't be difficult to | refactor. It should literally be a substitution. | | This is overstated. Not all abstractions are obvious | substitutions. To elaborate: languages vary in their | syntax, typing, and scope. So what might be an 'easy' | substitution and one language might not be easy in another. | Chris2048 wrote: | > figure out the abstraction after you need something a 3rd | time | | That's still too much of a "rule". | | Whenever I feel (or know) two functions are similar, the | factors that determine if I should merge them: | | - I see significant benefit too doing so, usually the benefit | of a utility that saves writing the same thing in the future, | or debugging the same/similar code repeatedly. | | - How likely the code is to diverge. Sometimes I just mark | things for de-duping, but leave it around a while to see if one | of the functions change. | | - The function is big enough it cannot just be in-lined where | it is called, and the benefit of de-duplication is not | outweighed by added complexity to the call stack. | jugg1es wrote: | Finally! I'm glad to hear I'm not the only one. I've gone against | 'Clean Code' zealots that end up writing painfully warped | abstractions in the effort to adhere to what is in this book. | It's OK to duplicate code in places where the abstractions are | far enough apart that the alternative is worse. I've had | developers use the 'partial' feature in C# to meet Martin's | length restrictions to the point where I have to look through | 10-15 files to see the full class. The examples in this post are | excellent examples of the flaws in Martin's absolutism. | tomnipotent wrote: | > where I have to look through 10-15 files to see the full | class | | The Magento 2 codebase is a good example of this. It's both | well written and horrible at the same time. Everything is so | spread out into constituent technical components, that the code | loses the "narrative" of what's going on. | nobodyandproud wrote: | > It's OK to duplicate code in places where the abstractions | are far enough apart that the alternative is worse. | | Something I've mentioned to my direct reports during code | reviews: Sometimes, code is duplicated because it just so | happens to do something similar. | | However, these are independent widgets and changes to one | should not affect the other; in other words, not suitable for | abstraction. | | This type of reasoning requires understanding the problem | domain (i.e., use-case and the business functionality ). | ericmcer wrote: | What do you say to convince someone? It's tricky to review a | large carefully abstracted PR that introduces a bunch of new | logic and config with something like: "just copy paste lol" | throwaway8581 wrote: | Sometimes you really just do need a 500 line function. | karmakaze wrote: | Yes, it's the first working implementation before good | boundaries are not yet known. After a while it becomes | familiar and natural conceptual boundaries arise that leads | to 'factoring' and shouldn't require 'refactoring' because | you prematurely guessed the wrong boundaries. | | I'm all for the 100-200 line working version--can't say I've | had a 500. I did once have a single SQL query that was about | 2 full pages pushing the limits of DB2 (needed multiple PTFs | just to execute it)--the size was largely from heuristic | scope reductions. In the end, it did something in about 3 | minutes that had no previous solution. | rzanella wrote: | Nah mate, you never do. Nor 500 1-liners. | throw1234651234 wrote: | In C# and .NET specifically, we find ourselves having a | plethora of services when they are "human-readable" and short. | | A service has 3 "helper" services it calls, which may, in turn | have helper services, or worse, depend on a shared repo | project. | | The only solution I have found is to move these helpers into | their own project, and mark the helpers as internal. This | achieves 2 things: | | 1. The "sub-services" are not confused as stand-alone and only | the "main/parent" service can be called. 2. The "module" can | now be deployed independently if micro-services ever become a | necessity. | | I would like feedback on this approach. I do honestly thing | files over 100 lines long are unreadable trash, and we have | achieved a lot be re-using modular services. | | We are 1.5 years into a project and our code re-use is sky- | rocketing, which allows us to keep errors low. | | Of course, a lot of dependencies also make testing difficult, | but allow easier mocks if there are no globals. | fastasucan wrote: | >I would like feedback on this approach. I do honestly thing | files over 100 lines long are unreadable trash | | Dunno if this is the feedback you are after, but I would try | to not be such an absolutist. There is no reason that a great | 100 line long file becomes unreadable trash if you add one | line. | throw1234651234 wrote: | I mean feedback on a way to abstract away helper services. | As far as file length, I realize that this is subjective, | and the 100 lines number is pulled out of thing air, but | extremely long files are generally difficult to read and | context gets lost. | username90 wrote: | Putting shared context in another file makes it harder to | read though. Files should be big enough to represent some | reasonable context, for more complicated things that | necessary creates a big shared context you want bigger | files and simpler things smaller files. | | A thing that can be perfectly encapsulated in a 1000 line | file with a small clean interface is much much better | than splitting that up into 20 files 100 lines each | calling each other. | qxmat wrote: | ... and here I was thinking I was alone! | ryanmarsh wrote: | I started OOP in '96 and I was never able to wrap my head | around the code these "Clean Code" zealots produced. | | Case in point: Bob Martin's "Video Store" example. | | My best guess is that clean code, to them, was as little code | on the screen as possible, not necessarily "intention revealing | code either", instead everything is abstracted until it looks | like it does nothing. | forgetfulness wrote: | SOLID code is a very misleading name for a technique that | seems to shred the code into confetti. | | I personally don't feel all that productive spending like | half my time just navigating the code rather than actually | reading it, but maybe it's just me. | zimpenfish wrote: | > I personally don't feel all that productive spending like | half my time just navigating the code rather than actually | reading it | | Had this problem at a previous job - main dev before I got | there was extremely into the whole "small methods" cult and | you'd regularly need to open 5-10 files just to track down | what something did. Made life - and the code - a nightmare. | astrange wrote: | I have had the experience of trying to understand how a | feature in a C++ project worked (both Audacity and Aegisub I | think) only to find that I actually could not find where | anything was implemented, because everything was just a glue | that called another piece of glue. | | Also sat in their IRC channel for months and the lead | developer was constantly discussing how he'd refactor it to | be cleaner but never seemed to add code that did something. | ziml77 wrote: | Seriously? That's an abuse of partial and just a way of | following the rules without actually following them. That code | must have been fun to navigate... | jugg1es wrote: | Can I please forward your contact info to my developer? Maybe | you can do a better job convincing him haha ;) | pdpi wrote: | Many years ago I worked on a project that had a hard "no hard | coded values" rule, as requested by the customer. The team | routinely wrote the equivalent to const | char_a = "a"; | | And I couldn't get my manager to understand why this was a | problem. | jandrese wrote: | Clearly it is still a hardcoded value! It fails the | requirement. Instead there should be a factory that loads a | file that reads in the "a" to the variable, nested down | under 6 layers of abstraction spread across a dozen files. | mkoryak wrote: | thats too enterprisey, at a startup you would write it | like this: | | const charA = (false+"")[1]; | bosswipe wrote: | That has three constants. | mkoryak wrote: | is this better? | | const charA = (![]+[])[+!+[]]; | pdpi wrote: | [] is still a constant. | mkoryak wrote: | True, but we can both agree that this is a better | constant than "a". Much better job security in that | code.. unless you get fired for writing it, that is | taeric wrote: | Saw this just the other day. I was at a loss to know what | to say. :( | mumblemumble wrote: | My last company was very into Clean Code, to the point where | all new hires were expected to do a book club on it. | | My personal take away was that there were a few good ideas, all | horribly mangled. The most painful one I remember was his | treatment of the Law of Demeter, which, as I recall, was so | shallow that he didn't even really even thoroughly explain what | the law was trying to accomplish. (Long story short, bounded | contexts don't mean much if you're allowed to ignore the | boundaries.) So most everyone who read the book came to | earnestly believe that the Law of Demeter is about period-to- | semicolon ratios, and proceeded to convert something like | val frobnitz = Frobnitz.builder() .withPixieDust() | .withMayonnaise() .withTarget(worstTweetEver) | .build(); | | into var frobnitzBuilder = Frobnitz.builder(); | frobnitzBuilder = frobnitzBuilder.withPixieDust(); | frobnitzBuilder = frobnitzBuilder.withMayonnaise(); | frobnitzBuilder = frobnitzBuilder.withTarget(worstTweetEver); | val frobnitz = frobnitzBuilder.build(); | | and somehow convince themselves that doing this was producing | tangible business value, and congratulate themselves for | substantially improving the long-term maintainability of the | code. | | Meanwhile, violations of the _actual_ Law of Demeter ran | rampant. They just had more semicolons. | in9 wrote: | I love how this is clearly a contextual recommendation. I'm | not a software developer, but a data scientist. In pandas, to | write your manipulations in this chained methods fashing is | highly encouraged IMO. It's even called "pandorable" code | mumblemumble wrote: | TBH, the only context where I've seen people express a | strong preference for the non-chained option is under the | charismatic influence of Uncle Bob's baleful stare. | | Otherwise, it seems opinions typically vary between a | strong preference for chaining, and rather aggressive | feelings of -\\_(tsu)_/- | mywittyname wrote: | The latter example (without the redundant assignments) is | preferred by people who do a lot of line-by-line debugging. | While most IDEs allow you to set a breakpoint in the middle | of an expression, that's still more complicated and error | prone than setting one for a line. | | I've been on a team that outlawed method chaining | specifically because it was more difficult to debug. Even | though I'm more of a method-chainer myself, I have taken to | writing unchained code when I am working on a larger team. | var frobnitzBuilder = Frobnitz.builder(); | frobnitzBuilder.withPixieDust(); | frobnitzBuilder.withMayonnaise(); | frobnitzBuilder.withTarget(worstTweetEver); val | frobnitz = frobnitzBuilder.build(); | | ...is undeniably easier to step-through debug than the | chained version. | mumblemumble wrote: | Might depend on the debugger? The main ones I've used | also let me go through the chained version one at a time, | including seeing intermediate values. | gmmeyer wrote: | wow this is a nightmare | TeMPOraL wrote: | On that note, I've never seen an explanation of Law of | Demeter that made any kind of sense to me. Both the | descriptions I read and the actual uses I've seen boiled down | to the type of transformation you just described, which is | very much pointless. | | > _Long story short, bounded contexts don 't mean much if | you're allowed to ignore the boundaries._ | | I'd like to read more. Do you know of a source that covers | this properly? | karmakaze wrote: | Three things that go well together: - Law | of Demeter - Tell, Don't Ask - narrow | interfaces | crdrost wrote: | Agree that the transformation described is pointless. | | A more interesting statement, but I am not sure it is | exactly equivalent to the law of Demeter: | | Distinguish first between immutable data structures (and | I'd group lambdas with them), and objects. An Object is | something more than _just_ a mutable data structure, one | wants to also fold in the idea that some of these objects | exist in a global namespace providing a named mutable state | to the entire rest of the program. And to the extent that | one thinks about threads one thinks about objects as | providing a shared-state multithread story that requires | synchronization, and all of that. | | Given that distinction, one has a model of an application | as kind of a factory floor, there are widgets (data | structures and side-effect-free functions) flowing between | Machines (big-o Objects) which process them, translate | them, and perform side-effecting I/O and such. | | Quasi-law-of-Demeter: in computing you have the power to | also send a Machine down a conveyor belt, and build other | Machines which can respond to that.[1] This is a tremendous | power and it comes with tremendous responsibility. Think a | system which has something like "Hey, we're gonna have an | Application store a StorageMechanism and then in the live | application we can swap out, without rebooting, a MySQL | StorageMechanism for a local SQLite Storage Mechanism, or a | MeticulouslyLoggedMySQL Storage Mechanism which is exactly | like the MySQL one except it also logs every little thing | it does to stdout. So when our application is misbehaving | we can turn on logging while it's misbehaving and if those | logs aren't enough we can at least sever the connection | with the live database and start up a new node while we | debug the old one and it thinks it's still doing some | useful work." | | The signature of this is being identified by this quasi- | Law-of-Demeter as this | "myApplication.getStorageMechanism().saveRecord(myRecord)" | chaining. The problem is not the chaining itself; the idea | would be just as wrong with the more verbose | "StorageMechanism s = myApp.getStorageMechanism(); | s.saveRecord(myRecord)" type of flow. The problem is just | that this superpower is really quite powerful and YAGNI | principles apply here: you probably don't need the ability | for an application to hot-swap storage mechanisms this | way.[2] | | Bounded contexts[3] are kind of a red herring here, they | are extremely handy but I would not apply the concept in | this context. | | 1. FWIW this idea is being shamelessly stolen from Haskell | where the conveyor belt model is an "Arrow" approach to | computing and the idea that a machine can flow down a | conveyor belt requires some extra structure, "ArrowApply", | which is precisely equivalent to a Monad. So the quasi-law- | of-Demeter actually says "avoid monads when possible", hah. | | 2. And of course you may run into an exception to it and | that's fine, if you are aware of what you are doing. | | 3. Put simply a bounded context is the programming idea of | "namespace" -- a space in which the same terms/words/names | have a different meaning than in some other space -- | applied to business-level speech. Domain-driven design is | basically saying "the words that users use to describe the | application, should also be the words that programmers use | to describe it." So like in original-Twitter the posts to | twitter were not called tweets, but now that this is the | common name for them, DDD says "you should absolutely | create a migration from the StatusUpdate table to the Tweet | table, this will save you incalculable time in the long-run | because a programmer may start to think of StatusUpdates as | having some attributes which users don't associate with | Tweets while users might think of Tweets as having other | properties like 'the tweet I am replying to' which | programmers don't think the StatusUpdates should have... | and if you're not talking the same language then every | single interaction consists of friction between you both." | The reason we need bounded contexts is that your larger | userbase might consist both of Accountants for whom a | "Project" means ABC, and Managers for whom a "Project" | means DEF, and if you try to jam those both into the | Project table because they both have the same name you're | gonna get hurt real bad. In turn, DDD suggests that once | you can identify where those namespace boundaries seem to | exist in your domain, those make good module boundaries, | since modules are the namespaces of our software world. And | then if say you're doing microservices, instead of pursuing | say the "strong entity" level of ultra-fine granularity, | "every term used in my domain deserves its own | microservice," try coarser-graining it by module boundaries | and bounded contexts, create a "mini-lith" rather than | these "nanoservices" that each manage one term of the | domain... so the wisdom goes. | mumblemumble wrote: | If you really want to dig into it, perhaps a book on | domain-driven design? That's where I pulled the term | "bounded context" from. | | My own personal oversimplification, probably grossly | misleading, is that DDD is what you get when you take the | Law of Demeter and run as far with it as you can. | TeMPOraL wrote: | Thanks. | | I have Evans' book on my bookshelf. I understand it's | _the_ book on DDD, right? I tried to read it a while ago, | I got through about one third of it before putting it | away. Maybe I should revisit it. | vidarh wrote: | > Law of Demeter | | "Don't go digging into objects" pretty much. | | Talk to directly linked objects and tell them what you need | done, and let them deal with their linked objects. Don't | assume that you know what is and always will be involved in | doing something on dependent objects of the one you're | interacting with. | | E.g. lets say you have a Shipment object that contains | information of something that is to be shipped somewhere. | If you want to change the delivery address, you should | consider telling the shipment to do that rather than | exposing an Address and let clients muck around with that | directly, because the latter means that now if you need to | add extra logic if the delivery address changes there's a | chance the changes leaks all over the place (e.g. you | decide to automate your customs declarations, and they need | to change if the destination country changes; or delivery | costs needs to updated). | | You'll of course, as always, find people that takes this | way too far. But the general principle is pretty much just | to consider where it makes sense to hide linked objects | behind a special purpose interface vs. exposing them to | clients. | Chris_Newton wrote: | _Talk to directly linked objects and tell them what you | need done, and let them deal with their linked objects. | Don 't assume that you know what is and always will be | involved in doing something on dependent objects of the | one you're interacting with._ | | IMHO, this is one of those ideas you have to consider on | its merits for each project. | | My own starting point is usually that I probably don't | want to drill into the internals of an entity that are | implementation details at a lower level of abstraction | than the entity's own interface. That's breaking through | the abstraction and defeating the point of having a | defined interface. | | However, there can also be relationships between entities | on the same level, for example if we're dealing with | domain concepts that have some sort of hierarchical | relationship, and then each entity might expose a link to | parent or child entities as part of its interface. In | that case, I find it clearer to write things like | if (entity.parent.interesting_property !== | REQUIRED_VALUE) { abort_invalid_operation(); | } | | instead of let parent_entity = | entities.find(entity.parent_id); if | (parent_entity.interesting_property !== REQUIRED_VALUE) { | abort_invalid_operation(); } | | and this kind of pattern might arise often when we're | navigating the entity relationships, perhaps finding | something that needs to be changed and then checking | several different constraints on the overall system | before allowing that change. | | The "downside" of this is that we can no longer test the | original entity's interface in isolation with unit tests. | However, if the logic requires navigating the | relationships like this, the reality is that individual | entities aren't independent in that sense anyway, so have | we really lost anything of value here? | | I find that writing a test suite at the level of the | overall set of entities and their relationships -- which | is evidently the smallest semantically meaningful data | set if we need logic like the example above -- works fine | as an alternative to dogmatically trying to test the | interface for a single entity entirely in isolation. The | code for each test just sets up the store of entities and | adds the specific instances and relationships I want for | each test, which makes each test scenario nicely | transparent. This style also ensures the tests only | depend on real code, not stand-ins like mocks or stubs. | rezonant wrote: | I don't think the two versions are relevant to Law of | Demeter. One example has pointers/references in a strong | tree and another has indexed ones, but neither is | embracing LoD more or less than the other. | | This would be a more relevant example: | | parent_entity.children.remove(this) | | vs | | parent_entity.remove_child(this) | | ...Where remove_child() would handle removing the entity | from `children` directly, and also perhaps busting a | cache, or notifying the other children that the heirarchy | has changed, etc etc. | | Going back to your original case, you _could_ argue that | LoD would advise you to create a method on entity which | _returns_ the parent, but I think that would fall under | encapsulation. If you did that though, you could hide the | implementation detail of whether `parent` is a reference | or an ID on the actual object, which is what most ORMs | will do for you. | barrkel wrote: | Ah, but what if children is some kind of List or | Collection which can be data-bound? By Liskov's | substition principle, you ought to be able to pass it to | a Collection-modifying routine and have it function | correctly. If the parent must be called the children | member should be private, or else the collection should | implement eventing and the two methods should have the | same effect (and ideally you'd remove one). | mumblemumble wrote: | That takes us back up to viardh's concluding remark from | earlier in the thread: | | > You'll of course, as always, find people that takes | this way too far. But the general principle is pretty | much just to consider where it makes sense to hide linked | objects behind a special purpose interface vs. exposing | them to clients. | | I would say that if you're using a ViewModel object that | will be data-bound, then you're sort of outside the realm | of the Law of Demeter. It's really more meant to concern | objects that implement business logic, not ones that are | meant to be fairly dumb data containers. | | On the other hand, if it is one that is allowed to | implement business logic, then I'd say, yeah, making the | children member public in the first place is violating | the law. You want to keep that hidden and supply a | remove_child() method instead, so that you can retain the | ability to change the rules around when and how children | are removed, _without_ violating LSP in the process. | rezonant wrote: | In the other branch I touched on this- iterating children | still being a likely use case after all, you have the | option of exposing the original or making a copy which | could have perf impacts. | | But honestly best to not preoptimize, I would probably do | | private _children : Entity[]; | | get children() { return this._children.slice(); } | | And reconsider the potential mutation risk later if the | profiler says it matters. | Chris_Newton wrote: | FWIW, I was writing JavaScript in that example, so | `entity.parent` might have been implemented internally as | a function anyway: get parent() { | return this.entities.find(this.parent_id); } | | I don't think whether we write `entity.parent` or | `entity.parent()` really matters to the argument, though. | | In any case, I see what you're getting at. Perhaps a | better way of expressing the distinction I was trying to | make is whether the nested object that is being accessed | in a chain can be freely used without violating any | invariants of the immediate object. If not, as in your | example where removing a child has additional | consequences, it is probably unwise to expose it directly | through the immediate object's interface. | rezonant wrote: | Yes, its a great case for making the actual `children` | collection private so that mutation must go through the | interface methods instead. But still, iteration over the | children is a likely use case, so you are left with | either exposing the original object or returning a copy | of the array (potentially slower, though this might not | matter depending). | Chris_Newton wrote: | That problem could potentially be solved if the | programming language supports returning some form of | immutable reference/proxy/cursor that allows a caller to | examine the container but without being able to change | anything. Unfortunately, many popular languages don't | enforce transitive immutability in that situation, so | even returning an "immutable" version of the container | doesn't prevent mutation of its contained values in those | languages. Score a few more points for the languages with | immutability-by-default or with robust ownership | semantics and support for transitive immutability... | mumblemumble wrote: | As for why this is useful: | | If objects are allowed to talk to friends of friends, | that greatly increases the level of interdependency among | objects, which, in turn, increases the number of | ancillary changes you might need to make in order to | ensure all the code talking to some object remains | compatible with its interface. | | More subtly, it's also a code smell that suggests that, | regardless of the presence of objects and classes, the | actual structure and behavior of the code is more | procedural/imperative than object-oriented. Which may or | may not be a big deal - the importance of adhering to a | paradigm is a decision only you can make for yourself. | bostonsre wrote: | I think following some ideas in the book, but ignoring others | like the ones applicable for the law of demeter can be a | recipe for a mess. The book is very opinionated, but if | followed well I think it can produce pretty dead simple code. | But at the same time, just like with any coding, experience | plays massively into how well code is written. Code can be | written well when using his methods or when ignoring his | methods and it can be written badly when trying to follow | some of his methods or when not using his methods at all. | littlecranky67 wrote: | The problem is that `partial` in C# should never even have been | considered as a "solution" to write small, maintainable | classes. AFAIK partial was introduced for code-behind files, | not to structure human written code. | | Anyways, you are not alone with that experience - a common | mistake I see, no matter what language or framework, is that | people fall for the fallacy "separation into files" is the same | as "separation of concerns". | JustSomeNobody wrote: | There seems to be a lot of overlap between the Clean Coders and | the Neo Coders [0]. I wish we could get rid of both. | | [0] People who strive for "The One" architecture that will | allow any change no matter what. Seriously, abstraction out the | wazoo! | | Honestly. If you're getting data from a bar code scanner and | you think, "we should handle the case where we get data from a | hot air balloon!" because ... what if?, you should retire. | buescher wrote: | I like to say "the machine that does everything, does | nothing". | FalconSensei wrote: | I think the problem is not the book itself, but people thinking | that all the rules apply to all the code, al the time. A length | restriction is interesting because it makes you think if maybe | you should spit your function into more than one, as you might | be doing too much in one place. Now, if splitting will make | things worse, then just don't. | SassyGrapefruit wrote: | You were never alone Juggles. We've been here with you the | whole time. | | I have witnessed more people bend over backwards and do the | most insane things in the name of avoiding "Uncle Bob's" | baleful stare. | | It turns out that following "Uncle Sassy's" rules will get you | a lot further. | | 1. Understand your problem fully | | 2. Understand your constraints fully | | 3. Understand not just where you are but where you are headed | | 4. Write code that takes the above 3 into account and make | sensible decisions. When something feels wrong ... don't do it. | | Quality issues are far more often planning, product management, | strategic issues than something as easily remedied as the code | itself. | blowski wrote: | "How do you develop good software? First, be a good software | developer. Then develop some software." | | The problem with all these lists is that they require a sense | of judgement that can only be learnt from experience, never | from checklists. That's why Uncle Bob's advice is | simultaneously so correct, and yet so dangerous with the | wrong fingers on the keyboard. | SassyGrapefruit wrote: | Yeah to some degree. I am in that 25 years of experience | range. The software I write today looks much more like year | 1 than year 12. The questions I ask in meetings I would | have considered "silly questions" 10 years ago. Turns out | there was a lot of common sense I was talked out of along | the way. | | Most people already know what makes sense. It's the | absurdity of office culture, JR/SR titles, and perverse | incentive that convinces them to walk in the exact opposite | direction. Uncle Bob is the culmination of that absurdity. | Codified instructions that are easily digested by the | lemmings on their way to the cliff's edge. | TeMPOraL wrote: | Agreed. | | That's why my advice to junior programmers is, pay | attention to how you _feel_ while working on your project - | especially, when you 're getting irritated. In particular: | | - When you feel you're just mindlessly repeating the same | thing over and over, with minor changes - there's probably | a structure to your code that you're manually unrolling. | | - As you spend time figuring out what a piece of code does, | try to make note of what made gaining understanding hard, | and what could be done to make it easier. Similarly, when | modifying/extending code, or writing tests, make note of | what took most effort. | | - When you fix a bug, spend some time thinking what caused | it, and how could the code be rewritten to make similar | bugs impossible to happen (or at least very hard to | introduce accidentally). | | Not everything that annoys you is a problem with the code | (especially when one's unfamiliar with the codebase or the | tooling, the annoyance tends to come from lack of | understanding). Not everything _should_ be fixed, even if | it 's obviously a code smell. But I found that, when I paid | attention to those negative feelings, I eventually learned | ways to avoid them up front - various heuristics that yield | code which is easier to understand and has less bugs. | | As for following advice from books, I think the best way is | to test the advice given by just applying it (whether in a | real or purpose-built toy project) and, again, observing | whether sticking to it makes you more or less angry over | time (and why). Code is an incredibly flexible medium of | expression - but it's not infinitely flexible. It will push | back on you when you're doing things the wrong way. | blowski wrote: | That's a great set of tips, thanks for sharing. | | I like the idea of trying to over-apply a rule on a toy | project, so you can get a sense of where it helps and | where it doesn't. For example, "build Conway's Game of | Life without any conditional branches" or "build FizzBuzz | where each function can have only one line of code". | dsego wrote: | > When you feel you're just mindlessly repeating the same | thing over and over, with minor changes - there's | probably a structure to your code that you're manually | unrolling. | | Casey has a good blog post about this where he explains | his compression-oriented programming, which is a | progressive approach, instead of designing things up | front. | | https://caseymuratori.com/blog_0016 | TeMPOraL wrote: | I read that a while ago. It's a great article, I second | the recommendation! I also love the term, "compression- | oriented programming", it clicked in my mind pretty much | the moment I saw it. | 3pt14159 wrote: | The right advice to give new hires, especially junior ones, | is to explain to them that in order to have a good first PR | they should read this Wikipedia page first: | | https://en.wikipedia.org/wiki/Code_smell | | Also helpful are code guidelines like the one that Google | made for Python: | | https://google.github.io/styleguide/pyguide.html | | Then when their first PR opens up, you can just point to | the places where they didn't quite get it right and | everyone learns faster. Mentorship helps too, but much of | software is self-learning and an hour a week with a mentor | doesn't change that. | 908B64B197 wrote: | I've seen juniors with better judgment than "seniors". | | But they were not in the same comp bracket. And I don't | think they gravitated in the same ecosystems so to speak. | mumblemumble wrote: | The profession needs a stronger culture of apprenticeship. | | In between learning the principles incorrectly from books, | and learning them inefficiently at the school of hard | knocks, there's a middle path of learning them from a | decent mentor. | titzer wrote: | > 1. Understand your problem fully | | > 2. Understand your constraints fully | | These two fall under _requirements gathering_. It 's so often | forgotten that software has a specific purpose, a specific | set of things it needs to do, and that it should be crafted | with those in mind. | | > 3. Understand not just where you are but where you are | headed | | And this is the part that breaks down so often. Because | software is simultaneously so easy and so hard to change, | people fall into traps both left and right, assuming some | dimension of extensibility that never turns out to be | important, or assuming something is totally constant when it | is not. | | I think the best advice here is that YAGNI, don't add | functionality for extension unless your requirements | gathering suggests you are going to need it. If you have | experience building a thing, your spider senses will perk up. | If you don't have experience building the thing, can you get | some people on your team that do? Or at least ask them? If | that is not possible, you want to prototype and fail fast. Be | prepared to junk some code along the way. | | If you start out not knowing any of these things, and also | never junking any code along the way, what are the actual | odds you got it right? | FalconSensei wrote: | >These two fall under requirements gathering. It's so often | forgotten that software has a specific purpose, a specific | set of things it needs to do, and that it should be crafted | with those in mind. | | I wish more developers would actually gather requirements | and check if the proposed solution actually solves whatever | they are trying to do. | | I think part of the problem is that often we don't use what | we work on, so we focus too much in the technical details, | but we forget what the user actually needs and what | workflow would be better. | | In my previous job, clients were always asking for changes | or new features (they paid dev hours for it) and would come | with a solution. But I always asked what was the actual | problem, and many times, there was a solution that would | solve the problem in a better way | hackinthebochs wrote: | >Write code that takes the above 3 into account and make | sensible decisions. When something feels wrong ... don't do | it. | | The problem is that people often need specifics to guide them | when they're less experienced. Something that "feels wrong" | is usually due to vast experience being incorporated into | your subconscious aesthetic judgement. But you can't rely on | your subconscious until you've had enough trials to hone your | senses. Hard rules can and often are overapplied, but its | usually better than the opposite case of someone without good | judgement attempting to make unguided judgement calls. | tarsinge wrote: | You are right, but also I think the problem discussed in | the article is that some of these hard rules are | questionable. DRY for example: as a hard rule it leads to | overly complex and hard to maintain code because of bad | and/or useless abstractions everywhere (as illustrated in | TFA). It needs either good experience to sense if they | "feel good" like you say, or otherwise proven repetitions | to reveal a relevant abstraction. | zamalek wrote: | I've also never agreed completely with Uncle Bob. I was an | OOP zealot for maybe a decade, and I'm now I'm a Rust | convert. The biggest "feature" of Rust is that is probably | brought semi-functional concepts to the "OOP masses." I found | that, with Rust, I spent far more time solving the problem at | hand... | | Instead of solving how I am going to solve the problem at | hand ("Clean Coding"). What a fucking waste of time, my brain | power, and my lifetime keystrokes[1]. | | I'm starting to see that OOP is more suited to programming | _literal_ business logic. The best use for the tool is when | you actually have a "Person", "Customer" and "Employee" | entities that have to follow some form of business rules. | | In contradiction to your "Uncle Sassy's" rules, I'm starting | to understand where "Uncle Beck" was coming from: | | 1. Make it work. | | 2. Make it right. | | 3. Make it fast. | | The amount of understanding that you can garner from make | something work leads very strongly into figuring out the best | way to make it right. And you shouldn't be making anything | fast, unless you have a profiler and other measurements | telling you to do so. | | "Clean Coding" just perpetuates all the broken promises of | OOP. | | [1]: https://www.hanselman.com/blog/do-they-deserve-the-gift- | of-y... | arthur_sav wrote: | I've gone through java code where i need to open 15 different | files, with one lined pieces of code, just to find out it's a | "hello world" class. | | I like abstraction as much as the next guy but this is closer | to obfuscation than abstraction. | gilbetron wrote: | At a previous company, there was a Clean Code OOP zealot. I | heard him discussing with another colleague about the need to | split up a function because it was too long (it was 10 | lines). I said, from the sidelines, "yes, because nothing | enhances readability like splitting a 10 line function into | 10, 1-line functions". He didn't realize I was being | sarcastic and nodded in agreement that it would be much | better that way. | Cthulhu_ wrote: | Spaghetti code is bad, but lasagna code is just as bad IMO. | [deleted] | WorldMaker wrote: | > It's OK to duplicate code in places where the abstractions | are far enough apart that the alternative is worse. | | I don't recall where I picked up from, but the best advice I've | heard on this is a "Rule of 3". You don't have a "pattern" to | abstract until you reach (at least) three duplicates. ("Two is | a coincidence, three is pattern. Coincidences happen all the | time.") I've found it can be a useful rule of thumb to prevent | "premature abstraction" (an understandable relative of | "premature optimization"). It is surprising sometimes the | things you find out about the abstraction only happen when you | reach that third duplicate (variables or control flow | decisions, for instance, that seem constants in two places for | instance; or a higher level idea of why the code is duplicated | that isn't clear from two very far apart points but is clearer | when you can "triangulate" what their center is). | krinchan wrote: | > "premature abstraction" | | Also known as, "AbstractMagicObjectFactoryBuilderImpl" that | builds exactly one (1) factory type that generates exactly | (1) object type with no more than 2 options passed into the | builder and 0 options passed into the factory. :-) | mumblemumble wrote: | I'm coming to think that the rule of three is important | within a fairly constrained context, but that other principle | is worthwhile when you're working across contexts. | | For example, when I did work at a microservices shop, I was | deeply dissatisfied with the way the shared utility library | influenced our code. A lot of what was in there was fairly | throw-away and would not have been difficult to copy/paste, | even to four or more different locations. And the shared | nature of the library meant that any change to it was quite | expensive. Technically, maybe, but, more importantly, | socially. Any change to some corner of the library needed to | be negotiated with every other team that was using that part | of the library. The risk of the discussion spiraling away | into an interminable series of bikesheddy meetings was always | hiding in the shadows. So, if it was possible to leave the | library function unchanged and get what you needed with a | hack, teams tended to choose the hack. The effects of this | phenomenon accumulated, over time, to create quite a mess. | twic wrote: | An old senior colleague of mine used to insist that if i | added a script to the project, i had to document it on the | wiki. So i just didn't add my scripts to the project. | bradstewart wrote: | Do you think that's a feature or a bug? | WorldMaker wrote: | I'd argue if the code was "fairly throw-away" it probably | did not meet the "Rule of 3" by the time it was included in | the shared library in the first place. | karmakaze wrote: | The Go proverb is "A little copying is better than a little | dependency." Also don't deduplicate 'text' because it's the | same, deduplicate implementations if they match in both | mechanism 'what' it does as well as their semantic usage. | Sometimes the same thing is done with different intents which | can naturally diverge and the premature deduplication is | debt. | twic wrote: | I don't hate the rule of 3. But i think it's missing the | point. | | You want to extract common code if it's the same now, and | _will always be the same in the future_. If it 's not going | to be the same and you extract it, you now have the pain of | making it do two things, or splitting. But if it is going to | be the same and you don't extract it, you have the risk of | only updating one copy, and then having the other copy do the | wrong thing. | | For example, i have a program where one component gets data | and writes it to files of a certain format in a certain | directory, and another component reads those files and | processes the data. The code for deciding where the directory | is, and what the columns in the files are, _must_ be the | same, otherwise the programs cannot do their job. Even though | there are only two uses of that code, it makes sense to | extract them. | | Once you think about it this way, you see that extraction | also serves a documentation function. It says that the two | call sites of the shared code are related to each other in | some fundamental way. | | Taking this approach, i might even extract code that is only | used once! In my example, if the files contain dates, or | other structured data, then it makes sense to have the | matching formatting and parsing functions extracted and | placed right next to each other, to highlight the fact that | they are intimately related. | btilly wrote: | You have a point for extracting exact duplicates that you | know will remain the same. | | But the point of the rule of 3 remains. Humans do a | horrible job of abstracting from one or two examples, and | the act of encountering an abstraction makes code harder to | understand. | WorldMaker wrote: | > You want to extract common code if it's the same now, and | will always be the same in the future. | | I suppose I take that as a _presumption_ before the Rule of | 3 applies. I generally assume /take for granted that all | "exact duplicates" that "will always be the same in future" | are going to be a single shared function anyway. The | duplication I'm concerned about when I think the Rule of 3 | comes into play is the _duplicated but diverging_. ( "I | need to do this thing like X does it, _but_ ...") | | If it's a simple divergence, you can add a flag sometimes, | but the Rule of 3 suggests that _sometimes_ duplicating it | and diverging it that second time "is just fine" (avoiding | potentially "flag soup") until you have a better handle on | the pattern for why you are diverging it, what abstraction | you might be missing in this code. | Jtsummers wrote: | The rule of three is a guideline or principle, not a strict | rule. There's nothing about it that misses the point. If, | from your experience and judgement, the code can be reused, | reuse it. Don't duplicate it (copy/paste or write it a | second time). If, from your experience and judgement, it | oughtn't be reused, but you later see that you were wrong, | refactor. | | In your example, it's about modularity. The directory logic | makes sense as its own module. If you wrote the code that | way from the start, and had already decoupled it from the | writer, then reuse is obvious. But if the code were tightly | coupled (embedded in some fashion) within the writer, than | rewriting it would be the obvious step because reuse | wouldn't be practical without refactoring. And unless you | can see how to refactor it already, then writing it the | second time (or third) can help you discover the actual | structure you want/need. | | As people become more experienced programmers, the good | ones at least, already tend to use modular designs and keep | things decoupled which promotes reuse versus copy/paste. In | that case, the rule of three gets used less often by them | because they have fewer occurrences of real duplication. | dragonsky67 wrote: | I think the point you and a lot of other commenters make | is that applying hard and fast rules without referring to | context is simply wrong. Surely if all we had to do was | apply the rules, somebody would have long ago written a | program to write programs. ;-) | ozim wrote: | People to people dealt this fate ... | | What is mostly surprising I find most of developers are trying | to obey the "rules". Code containing even minuscule duplication | must be DRYied, everyone agrees that code must be clean and | professional. | | Yet it is never enough, bugs are showing up and stuff that was | written by others is always bad. | | I start thinking that 'Uncle Bob' and 'Clean code' zealots are | actually harmful, because it prevents people from taking two | steps back and thinking about what they are doing. Making | microservices/components/classes/functions that end up never | reused and making DRY holy grail. | | Personally I am YAGNI > DRY and a lot of times you are not | going to need small functions or magic abstractions. | cerved wrote: | Partial classes is an ugly hack to mix human and machine | generated source code. IMHO it should be avoided | Regic wrote: | I think the book is still useful with all it's flaws, mainly | because "overengineering code" is like exercising too much: sure, | it happens to some and can be a big issue, but for the vast | majority of people it's the opposite that is the problem. | [deleted] | jeffreyrogers wrote: | Most of these types of books approach things from the wrong | direction. Any recommendation should look at the way well | designed, maintainable systems are actually written and draw | their conclusions from there. Otherwise you allow too much | theorizing to sneak in. Lots of good options to choose from and | everyone will have their own pet projects, but something like | SQLite is probably exemplary of what a small development team | could aim for, Postgres or some sort of game engine would maybe | be good for a larger example (maybe some of the big open source | projects from major web companies would be better, I don't know). | | There are books that have done something like this[0], but they | are a bit high level. There is room for something at a lower | level. | | [0]: http://aosabook.org/en/index.html for example. | dsego wrote: | I would say Code Complete is one such book. | aristofun wrote: | I'd say "Stop recommending Clean Code to noobs! It's dangerous" | | Because Clean Code is really a good book once you have enough | experience to understand when each idea works and when does not. | And why. | | And noobs tend to over-engineer, so any book like CC or design | patterns give them additional excuse and reason to over engineer | and make a mess. | thrower123 wrote: | Clean Code is fine. It's a little dated, as you would expect, and | for the most part, everything of value in it has been assimilated | into the best practices and osmotic ether that pervades software | development now. It's effectively all the same stuff as you see | in Refactoring or Code Complete or Pragmatic Programmer. | | I suspect a lot of backlash against it centers around Uncle Bob's | less than progressive political and social stances in recent | years. | commandlinefan wrote: | TFA really seems to be saying that there's no such thing as | clean code, so don't even bother trying. | simias wrote: | I never read Clean Code and know nothing about its author so | I'm willing to trust you on the first part, but the second | paragraph is really uncalled for IMO. The article is long and | gives precise examples of its issues with the book. Assuming an | ulterior motive is unwarranted. | thrower123 wrote: | I don't think it is uncalled for when there have been several | instances of boycotts organized for that reason. | | Nobody is that upset about anodyne OOP design patterns | watwut wrote: | I did actually found the SetupTeardownIncluder class author | complains about really easier to read and interpret then the | original version. I know from the start what the intention was | and where I should go if I have issue with some part of the code. | | I dont even take issue with name. It makes it easy to find the | class just by remembering what it should do. I dont really care | all that much about verbs vs nouns. I want to be able to find the | right place by having rough idea about what it should do. I want | to get hint about class functionality from its name too. | mabbo wrote: | I think one should always be careful not to throw out the baby | with the bathwater[0]. | | Do I force myself to follow every single crazy rule in Clean | Code? Heck no. Some of them I _don 't agree with_. But do I find | myself to be a better coder because of what I learned from Bob | Martin? Heck yes. Most of the points he makes are insightful and | I apply them daily in my job. | | Being a professional means learning from many sources and knowing | that there's something to learn from each of them- and some | things to ignore from each of them. It means trying the things | the book recommends, and judging the results yourself. | | So I'm going to keep recommending Clean Code to new developers, | in the hopes that they can learn the good bits, and learn to | ignore the bad bits. Because so far, I haven't found a book with | more good bits (from my perspective) and fewer bad bits (from my | perspective). | | [0]https://en.wikipedia.org/wiki/Don%27t_throw_the_baby_out_wit.. | . | dsego wrote: | I would recommend Code Complete 2 over Clean Code. | AnimalMuppet wrote: | Por que no los dos? Read them both. Take the good parts of | each. Ignore the bad parts of each. | commandlinefan wrote: | That was my thought - neither one is SICP, you can finish | reading both in a week. | [deleted] | flatlin3 wrote: | Perfect, Its definitly my personal impression, but while | reading the post it looks like the author was looking for a | "one size fits all" book and was dissapointed they did not find | it. | | And to be honest that book will never exist, every knowledge | contributes to growing as a professional, just make sure to | understand, discuss, and use it (or not) for a real reason, not | just becaue its on book A or B. | | Its not like people need to choose one book and follow it | blindly for the rest of their lives, read more books :D | oriolid wrote: | In my opinion the problem is not that the rules are not one | size fits all, but that they are so misguided that Martin | himself couldn't come up with a piece of code where they | would lead to a good result. | theflyinghorse wrote: | Robert Martin and his aura always struck me as odd. In part | because of how revered he always was at organizations I worked. | Senior developers would use his work to end arguments, and many | code reviews discussions would be judged by how closely they | adhere to Clean Code. | | Of course reading Clean Code left me more confused than | enlightened due precisely to what he presents as good examples of | Code. The author of the article really does hit the nail on the | head about Martin's code style - it's borderline unreadable a lot | of times. | | Who the f. even is Robert Martin?! What has he built? As far as I | am able to see he is famous and revered because he is famous and | revered. | pydry wrote: | I think part of his appeal lies in his utter certainty that he | is correct. This is also the problem with him. | jacquesm wrote: | Like everything else: it's fine in moderation. Newbies should | practice clean code, everybody else gets to make their own | decisions. Treating anything as dogma whether it is Clean Code, | TDD, Agile or whatever is the flavor of madness of the day is | going to lead to suboptimal outcomes. And they're also a great | way to get rid of your most productive and knowledgeable team | members. | | So apply with caution and care and you'll be fine. | raverbashing wrote: | Yeah I agree with the author, and I would go further, it's a nice | list of reasons why Uncle Bob is insufferable. | | Because of stuff like this: | | > Martin's reasoning is rather that a Boolean argument means that | a function does more than one thing, which it shouldn't. | | Really? Really? Not even for dependency injection? Or, you know, | you should duplicate your function into two very similar things | to have one with the flag and another one without. Oh but DRY. | Sure. | | > . He says that an ideal function has zero arguments (but still | no side effects??), and that a function with just three arguments | is confusing and difficult to test | | Again, really? | | I find it funny who treats him as a guru. Or maybe that's the | right way to treat him, like those self-help gurus with | meaningless guidance and whishy-washy feel-good statements. | | > Every function in this program was just two, or three, or four | lines long. Each was transparently obvious. Each told a story. | And each led you to the next in a compelling order. | | Wow, illumination! Salvation! Right here! | | Until, of course you have to actually maintain this and has to | chase down 3 or 4 levels of functions deep what is it that the | code is actually doing. And think of function signature for every | minor thing. And passing all the arguments you need (ignoring | that "perfect functions have zero arguments" above - good luck | with that) | | Again, it sounds like self-help BS and not much more than that. | ziml77 wrote: | The boolean issue is probably the one that's caused me the most | pain. That contradiction with DRY has actually had me go back | and forth between repeating myself and using a flag, wasting a | ton of time on something incredibly pointless to be thinking | that hard about. I feel like the best thing for my career would | have been to _not_ read that book right when I started my first | professional programing job. | vbezhenar wrote: | It might make sense to divide your function with boolean flag | into two functions and extract common code into third private | function. Or may be it'll make things ugly. | | I treat those books as something to show me how other people | do things. I learn from it and I add it to my skill book. | Then I'll apply it and see if I like it. If I don't like it | in this particular case, I'll not apply it. IMO it's all | about having insight into every possible solution. If you can | implement something 10 different ways, you can choose the | best one among them, but you have to learn those 10 different | ways first. | bostonsre wrote: | It's been a while since I've read it, but I think to handle | boolean flag type logic well he suggests to rely on object | subclassing instead. So, for an example that uses a dry run | flag for scary operations, you can have your normal object | (a) and all of its methods that actually perform those scary | operations. Then you can subclass that object (a) to create a | dry run subclass (b). That object b, can override only the | methods that perform the scary operations that you want to | dry run while at the same time using all of its non scary | methods. That would let you avoid having if dry_run == true; | then dry_run_method() else scary_method() scattered in lots | of different methods. | bostonsre wrote: | > Really? Really? Not even for dependency injection? Or, you | know, you should duplicate your function into two very similar | things to have one with the flag and another one without. Oh | but DRY. Sure. | | I'm not sure dependency injection has anything to do with | boolean flags or method args. I think the key point here is | that he is a proponent of object oriented programming. I think | he touches on dependency injection later in the book, but it's | been a while since I've read it. He suggests your dependencies | get passed at object initialization, not passed as method | options. That let's you mock stuff without needing to make any | modifications to the method that uses that dependency easily. | | > Until, of course you have to actually maintain this and has | to chase down 3 or 4 levels of functions deep what is it that | the code is actually doing. And think of function signature for | every minor thing. And passing all the arguments you need | (ignoring that "perfect functions have zero arguments" above - | good luck with that) | | I myself find it easier to read and understand simple functions | than large ones with multiple indentation levels. Also, it | definitely does not make sense to pass many arguments along | with those many small functions. He recommends making them | object instance properties so that you don't need to do that. | | It may not be for everyone, but I'll take reading code that | follows his principles instead of code that had no thought | about design put into it any day of the week. It's not some | dogmatic law that should be followed in all cases, but to me | it's a set of pretty great ideas to keep in mind to lay out | code that is easy to maintain and test. | majormajor wrote: | > I'm not sure dependency injection has anything to do with | boolean flags or method args. | | DI can be abused as a way to get around long function | signatures. "I don't take a lot of arguments here" (I'm just | inside of an object that has ten other dependencies injected | in). Welcome back to testing hell. | bostonsre wrote: | Yea... that could probably turn ugly in a lot of cases. I | think the flow of object creation and dependency injection | would be the important part to handle well in a case with a | lot of dependencies. I think the dependencies should be | passed down through objects in one direction. So if your | object (a) that works on objects (b) that have a lot of | dependencies, that first outer object (a) is responsible | for injecting dependencies into those 2nd objects (b). So | if you have a mocked dependency, you pass that when | initializing object a, and object a is responsible for | injecting that mocked dependency into object b. | | A disclaimer, I'm an sre, so definitely not the most expert | proponent of oop. | majormajor wrote: | > Until, of course you have to actually maintain this and has | to chase down 3 or 4 levels of functions deep what is it that | the code is actually doing. | | The art is to chain your short functions like a paragraph, not | to nest them a mile deep, where the "shortness" is purely an | illusion and the outer ones are doing tons of things by calling | the inner ones. | | That's a lot harder, though. | | But it fits much better with the spirit of "don't have a lot of | args for your functions" - if you're making deeply nested | calls, you're gonna have to pass all the arguments the inner | ones need through the outer ones. Or else do something to | obfuscate how much you're passing around (global deps/state, | crazy amounts of deep DI, etc...) which doesn't make testing | any easier. | Kototama wrote: | It's actually a good marketing trick. He can sell something | slightly different and more "pure" and make promises on it and | then sell books, trainings and merchandises. | | That's what the wellness industry do all the time. | AngeloAnolin wrote: | Pardon me for stating this but pundits of the Clean Code mantra | I've worked with tend to be those consultants who bill enormous | amount of money to ensure that they have lengthy contracts which | is justified by wrapping codes in so much classes to abstract it | and considered *CLEAN* and *TESTABLE*. | | They will preach the awesomeness of clean code in terms of | maintainability, scalability and all those fancy enter-pricey | terms that at the end of the day, brought not enough value to | justify their cost. | jammycakes wrote: | The big problem that I have with Clean Code -- and with its | sequel, Clean Architecture -- is that for its most zealous | proponents, it has ceased to be a means to an end and has instead | become an end in itself. So they'll justify their approach by | citing one or other of the SOLID principles, but they won't | explain what benefit that particular SOLID principle is going to | offer them in that particular case. | | The point that I make about patterns and practices in programming | is that they need to justify their existence in terms of value | that they provide to the end user, to the customer, or to the | business. If they can't provide clear evidence that they actually | provide those benefits, or if they only provide benefits that the | business isn't asking for, then they're just wasting time and | money. | | One example that Uncle Bob Martin hammers home a lot is | separation of concerns. Separation of concerns can make your code | a lot easier to read and maintain if it's done right -- unit | testing is one good example here. But when it ceases to be a | means to an end and becomes an end in itself, or when it tries to | solve problems that the business isn't asking for, it degenerates | into speculative generality. That's why you'll find project after | project after project after project after project with cumbersome | and obstructive data access layers just because you "might" want | to swap out your database for some unknown mystery alternative | some day. | pjmlp wrote: | For me all this kind of stuff is only to sell books, conferences | and consulting services, and a big headache when working in teams | whose architects have bought too much into it. | lisper wrote: | So many coding recommendations trip up when they fail to take | into account Ron's First Law: all extreme positions are wrong. | Functions that are too long are bad, but functions that are too | short are equally bad. 2-4 lines? Good grief! That's not even | enough for a single properly formatted if-then-else! | | IMHO it's only when you can't see the top and bottom of a | function on the screen at the same time that you should start to | worry. | nicklecompte wrote: | I don't disagree with the overall message or choice of examples | behind this post, but one paragraph stuck out to me: | | > Martin says that it should be possible to read a single source | file from top to bottom as narrative, with the level of | abstraction in each function descending as we read on, each | function calling out to others further down. This is far from | universally relevant. Many source files, I would even say most | source files, cannot be neatly hierarchised in this way. | | The _relevance_ is a fair criticism but most programs in most | languages can in fact be hierarchized this way, with the small | number of mutually interdependent code safely separated. Many | functional languages actually enforce this. | | As an F# developer it can be very painful to read C# _programs_ | even though I often find C# _files_ very elegant and readable: it | just seems like a book, presented out of order, and without page | numbers. Whereas an .fsproj file provides a robust reading order. | dmurray wrote: | > "with the level of abstraction in each function descending as | we read on, each function calling out to others further down." | ... | | > Many functional languages actually enforce this. | | Don't they enforce the opposite? In ML languages (I don't know | F# but I thought it was an ML dialect), you can generally only | call functions that were defined _previously_. | | Of course, having a clear hierarchy is nice whether it goes | from most to least abstract, or the other way around, but I | think Martin is recommending the opposite from what you are | used to. | marcosdumay wrote: | > In ML languages, you can generally only call functions that | were defined previously. | | Hum... At least not in Haskell. | | Starting with the mostly dependent code makes a large | difference in readability. It's much better to open your file | and see what are the overall functions. The alternative is | browsing to find it, even when it's on the bottom. Since you | read functions from the top to the bottom, locating the | bottom of the function isn't much of a help to read it. | | 1 - The dependency order does not imply on any ordering in | abstraction. Both can change in opposite directions just as | well as on the same. | valenterry wrote: | ML languages are not functional ("functional" in the original | sense of the word - pure functional). They are impure and | thus don't enforce it. | nicklecompte wrote: | As someone who almost exclusively uses functional | languages: don't do this. This kind of pedantic gatekeeping | is not only obnoxious... it's totally inaccurate! Which | makes it 100x as obnoxious. | | "Functional" means "functions are first-class citizens in | the language" and typically mean a lot of core language | features designed around easily creasing android | manipulating functions as ordinary objects (so C#, C++, and | Python don't really count, even with more recent bells-and- | whistles). Typically there is a strong emphasis on | recursive definitions. But trying to pretend "functional | programming languages" are anything particularly specific | is just a recipe for dumb arguments. And of course, outside | of the entry point itself, it is quite possible (even | desirable) to write side-effect-free idiomatic ISO C. | | The "original" functional language was LISP, which is | impure as C and not even statically-typed - and for a long | time certain Lisp/Scheme folks would gatekeep about how a | language wasn't a Real Functional Language if it wasn't | homoiconic and didn't have fancy macros. (And what's with | those weird ivory tower Miranda programmers?) In fact, I | think the "gate has swung," so to speak, to the point that | people downplay the certain advantages of Lisps over | ML/Haskells/etc. | sedachv wrote: | > for a long time certain Lisp/Scheme folks would | gatekeep about how a language wasn't a Real Functional | Language if it wasn't homoiconic and didn't have fancy | macros | | This never happened. You are confusing functional with | the 2000s "X is a good enough Lisp" controversy, which | had nothing to do with functional programming. | | > "Functional" means "functions are first-class citizens | in the language" | | No, the word function has a clearly defined meaning. I | don't know where you get your strange ideas from - you | need to look at original sources. The word "functional" | did not become part of the jargon until the 1990s. Even | into the 1980s most people referred to this paradigm as | "applicative" (as in procedure application), which is a | lot more appropriate. The big problem with the Lisp | community is that early on, when everyone used the words | "procedures" or "subroutines," they decided to start | calling them "functions," even though they could have | side effects. This is probably the reason why people | started trying to appropriate "functional" as an | adjective from the ML and Haskell communities into their | own languages. A lot of people assume that if you can | write a for loop as a map, it makes it "functional." What | you end up with is a bunch of inappropriate cargo-culting | by people who do not understand the basics of functional | programming. | nicklecompte wrote: | Hmm, perhaps I am misreading this? Your understanding of ML | languages is correct. I have always found "Uncle Bob" | condescending and obnoxious so I can't speak to the actual | source material. | | I am putting more emphasis on the "reading top-to-bottom" | aspect and less on the level of abstraction itself (might be | why I'm misreading it). My understanding was that Bob sez a | function shouldn't call any "helper" functions until the | helpers have been defined - if it did, you wouldn't be able | to "read" it. But with your comment, maybe he meant that you | should define your lower-level functions as prototypes, | implement the higher-level functions completely, then fill in | the details for the lower functions at the bottom. Which is | situationally useful but yeah, overkill as a hard rule. | | In ML and F# you can certainly call interfaces before | providing an implementation, as long as you define the | interface first. Whereas in C# you can define the interface | last and call it all you want beforehand. This is what I find | confusing, to the point of being bad practice in most cases. | | So even if I misread specifically what (the post said) Bob | was saying, I think the overall idea is what Bob had in mind. | tsimionescu wrote: | It seems your idea is precisely the opposite of Robert | Martin's. What he is advocating for is starting out the | file with the high-level abstractions first, without any of | the messy details. So, at the top level you'd have a | function that says `DoTheThing() { doFirstPart(); | doSecondPart();}`,then reading along you'd find out what | doFirstPart() and doSecondPart() mean (note that I've used | imperative style names, but that was a random choice on my | part). | | Personally I prefer this style, even though I dislike many | other ideas in Clean Code. | | The requirement to define some function name before you can | call it is specific to a few languages, typically older | ones. I don't think it's very relevant in this context, and | there are usually ways around it (such as putting all | declarations in header files in C and C++, so that the | actual source file technically begins with the declarations | from the compiler's perspective, but doesn't actually from | a programmer perspective (it just begins with #include | "header"). | Izkata wrote: | > I am putting more emphasis on the "reading top-to-bottom" | aspect and less on the level of abstraction itself (might | be why I'm misreading it). My understanding was that Bob | sez a function shouldn't call any "helper" functions until | the helpers have been defined - if it did, you wouldn't be | able to "read" it. But with your comment, maybe he meant | that you should define your lower-level functions as | prototypes, implement the higher-level functions | completely, then fill in the details for the lower | functions at the bottom. | | Neither really, he's saying the higher-level abstractions | go at the top of the file, meaning the helpers go at the | bottom and get used before they're defined. No mention of | prototypes that I remember. | | Personally, I've never liked that advice either - I always | put the helpers at the top to build up the grammar, then | the work is actually done at the bottom. | throw1234651234 wrote: | We follow this approach closely - the problem is that people | confuse helper services for first-order services and call them | directly leading to confusion. I don't know how to avoid this | without moving the "main" service to a separate project and | having `internal` helper services. DI for class libraries in | .NET Core is also hacky if you don't want to import every | single service explicitly. | nicklecompte wrote: | Is there a reason why private/internal qualifiers aren't | sufficient? Possibly within the same namespace / partial | class if you want to break it up? | | As I type this out, I suppose "people don't use access | modifiers when they should" is a defensible reason.... I also | think the InternalsVisibleTo attribute should be used more | widely for testing. | throw1234651234 wrote: | You can't make a helper private if you move it out to a | separate service. The risk of making it public is if people | think the helper should be used directly, and not through | the parent service. | | Internal REQUIRES that the service be moved out to a class | library project, which seems like overkill in a lot of | cases. | golergka wrote: | Great! While we're on it, can we retire the gang of four as well? | I mean, the authors are obviously great software engineers, and | the Patterns have helped to design, build, and most importantly | read, a lot of software. But as we move forward, more and more of | these goals can be achieved much more elegantly and sustainably | with new languages and more functional approaches. Personally, I | find re-teaching junior programmers, who are still trying to make | everything into a class, very tiring. | incrudible wrote: | My biggest gripe: Functions shouldn't be short, they should be of | _appropriate size_. They should contain all the logic that isn 't | supposed to be exposed to the outside for someone else to call. | If that means your function is 3000 lines long, so be it. | | Realize that your whole program is effectively _one big function_ | and you achieve nothing by scattering its guts out into | individual sub-functions just to make the pieces smaller. | | If something is too convoluted and does too much, or has too much | redundancy, you'll know, because it'll cause problems. It'll | bother you. You shouldn't pre-empt this case by just writing | small functions by default. That'll just cause its own problems. | flipgimble wrote: | I never recommended Clean Code, but I've become a strong advocate | against it on teams that I lead after reading opinions by Bob | Martin such as this one: https://blog.cleancoder.com/uncle- | bob/2017/01/11/TheDarkPath.... That whole article reads as | someone who is stuck in their old ways and inflexible, then given | their large soapbox tries to justify their discomfort and | frustration. I consider Swift, Kotlin (and Rust) to be one of the | most important language evolutions that dramatically improved | software quality on the projects I've worked on. | | I've seen so many real world counter-examples to arguments made | in that article and his other blog posts that I'm puzzled why | this guy has such a large and devoted following. | wildermuthn wrote: | Actually, I found the post you linked to fairly logical. He's | saying that humans are inherently lazy, and that a language | that gives us the option between being diligent (strong types) | or being reckless (opt-out of strong types) will lead to the | worst form of recklessness: opting out while not writing tests, | giving the misimpression of safety. | | His point is that you can't practically force programmers to be | diligent through safety features of a language itself, since | edge-cases require escape hatches from those safety features, | and those safety hatches will be exploited by our natural | tendency to avoid "punishment". | | I'm not sure I agree with his point, but I don't find it an | unreasonable position. I'd be curious if Rust has escape | hatches that are easily and often abused. | | My favorite example here, and a counterpoint to Bob, is | Reacts's dangerously-unsafe-html attribute. I haven't seen it | in years (to the point where I can't recall the exact naming), | and perhaps it was removed at some point. But it made the | escape hatch really painful to use. And so the pain of using | the escape hatch made it less painful to actually write React | in the right manner. Coming from Angular, I think I struggled | at first with thinking I had to write some dangerous html, but | over time I forgot the choice of writing poor React code even | existed. | | So I guess I disagree with Bob's post here. It is possible to | have safety features in languages that are less painful than | the escape-hatches from those safety features. And no suite of | tests will ever be as powerful as those built-in safety | features. | flipgimble wrote: | He actually misunderstands and mischaracterizes the features | of the languages he complains about. These features remove | the need for a developer to keep track of invariants in their | code, so should be embraced and welcomed by lazy developers | who don't have to simulate the boring parts of code in their | head to make sure it works. "If it type-checks then it works" | philosophy really goes a long way toward relieving | developer's stress. | | For example, if I'm using C or Java I have take into account | that every pointer or reference can be null, at every place | where they are used. I should write null checks, (or error | checks say from opening a file handle) but I usually don't | because I'm lazy, or I forget, or its hard to keep track of | all possible error conditions. So I'm stressed during a | release because I can't predict the input that may crash my | code. | | In a language like Swift I am forced to do a null or an error | check once in the code, and for that small effort the | compiler will guarantee I will never have to worry about | these error conditions again. This type system means I can | refactor code drastically and with confidence, and I don't | have to spend time worrying about all code paths to see if | one of them would result in an unexpected null reference. On | a professional development team, it should be a no-brainer to | adopt a new technology to eliminate all null-reference | exceptions at runtime, or use a language to setup guarantees | that will hold under all conditions and in the future | evolution of the code. | | Worse than that, he sets up a patronizing and misguided | mental image of a common developer who he imagines will use a | language with type safety just to disable and abuse all the | safety features. Nobody does that, in my experience of | professional Swift, Kotlin or rust development. | | He advocates for unit tests only and above all else. That is | also painfully misguided: a test tells you it passes for one | given example of input. In comparison a good type system | guarantees that your code will work for ALL values of a given | type. Of course type systems can't express all invariants, so | there is a need for both approaches. But that lack of nuance | and plain bad advice turned me into an anti-UncleBob | advocate. | onehair wrote: | 1. Clean Code is not a fixed destination to which you'll ever | arrive. It's a way of life. | | 2. We might not use the same methods to write clean code. But | when you see clean code, you know it is clean. | | 3. Some traits clean code can have - When you | read the function name, you understand what the function does | - When you read the content of a function, you can understand | what it is about without reading line by line. - When you | try to refactor clean code, you find yourself sometimes ending up | only changing one cog in the whole system. | | 0. Real clean code does not exist. | asimpletune wrote: | I'd like to say that software engineering is a lot like playing | jazz. It's really hard for the beginner to know where to start, | and there're also endless sources for the "right" way to do | things. | | In truth however, like playing jazz, the only thing that _really_ | matters is results, and even those _can_ be subjective. You can | learn and practice scales all day long, but that doesn 't | _really_ tell you how to make music. | | I developed a style of software engineering that works really | well for me. It's fast, accurate, and naturally extensible and | easily refactorable. However, for various reasons, I've never | been able to explain it junior (or even senior) engineers, when | asked about why I code a certain way. At a certain point, it's | not the material that matters, but the audience's ability to | really _get_ what 's at the heart of the lesson. | | Technically, you could say something that's indisputable | accurate, like, "there're only 12 notes in an (western) octave, | and you just mix them until they sound good", but that's | obviously true to a degree that's fundamentally unhelpful. At the | same time, you could say "A good way to think about how to use a | scale is to focus less on the notes that you play and more on the | ones you don't". This is better advice but it may altogether be | unhelpful, because it doesn't really yet strike at the true heart | of what holds people back. | | So at a certain point, I don't really know if anyone can be | taught something as fundamentally "artful" (I.e. a hard thing | largely consisting of innumerable decisions that are larger | matters of taste - which is a word that should not be confused | with mere "opinion") as software engineering or jazz music. This | is because teaching alone is just not enough. At a certain point | people just have to learn for themselves, and obviously the | material that's out there is helpful, but I'm not sure if | anything can ever be explained so well as to remove the need for | the student at a certain point to simply "feel" what sounding | good sounds like, or what good software engineering feels like. | | I'll add one last thing. Going back to what I was saying about | not being able to explain to "junior (or even senior)" engineers. | Were the same "lesson" to happen with someone who is very, very | advanced, like a seasoned principal engineer who's built and | delivered many things, time and time again, across many different | engineering organizations and different technologies - someone | like a jazz music great for example. Anything I would have to say | on _my_ approach to such a software engineer would be treated as | obvious and boring, and they 'd probably much rather talk about | something else. I don't say this because I mean to imply that | whatever I would have to say is wrong or incorrect, but rather at | a certain level of advancement, you forget everything that you | know and don't remember what it took to get there. There are a | few who have a specific passion for teaching, but that's | orthogonal to the subject. | | I think it was Bill Evans who said something like "it takes years | of study and practice to learn theory and technique, but it takes | still a lifetime to forget it all". When you play like you've | forgotten it all, _that_ is when you achieve that certain _sound_ | in jazz music. Parenthetically, I 'll add that doesn't mean you | can't sound good being less advanced, but there's a certain | _sound_ that I 'm trying to tie together with this metaphor | that's parallel to great software engineering from knowledge, | practice, and then the experience to forget it all and simply | _do_. | | I think that's fundamentally what's at the heart of the matter, | not that it takes anyone any closer to getting there. You just | have to do it, because we don't really know how to teach how to | do really hard things in a way that produces reproducible | results. | brundolf wrote: | > This is done as part of an overall lesson in the virtue of | inventing a new domain-specific testing language for your tests. | I was left so confused by this suggestion. I would use exactly | the same code to demonstrate exactly the opposite lesson. Don't | do this! | | This example (code is in the article) was very telling of the | book author's core philosophy. | | Best I can tell, the OOP movement of the 2000s (I wasn't a | professional in 2008, though I was learning Java at the time) was | at its heart rooted in the idea that abstractions are nearly | always a win; the very idealistic perspective that anything you | can possibly give a name to, should be given a name. That | programmers down the line will _thank_ you for handing them a | named entity instead of perhaps even a single line of underlying | code. | | This philosophy greatly over-estimates the value, and greatly | under-estimates the cost, of _idea-creation_. I don 't just write | some code, I create an _idea_ , and then I write a bit of code as | implementation details for it. This is a very tantalizing vision | of development: all messy details are hidden away, what we're | left with is a beautiful constellation of ideas in their purest | form. | | The problem is that when someone else has to try and make sense | of your code, they first have to internalize all of your _ideas_ | , instead of just reading the code itself which may be calling | out to something they already understand. It is the opposite of | self-documenting code: it's code that requires its own _glossary_ | in addition to the usual documentation. "wayTooCold()" may read | more naturally to the person who wrote it, but there's a fallacy | where they assume that that also applies to other minds that come | along and read it later. | | Establishing a whole new concept with its own terminology in your | code is _costly_. It has to be done with great care and only when | absolutely necessary, and then documented thoroughly. I think as | an industry we have more awareness of this nowadays. We don 't | just make UML diagrams and kick them across the fence for all | those mundane "implementation details" to be written. | phendrenad2 wrote: | Sad to see the top comments are "Yes clean code leads to bad | code" and not "TOO MUCH clean code leads to bad code". Excluded | middle much? | strict9 wrote: | 10 or so years ago when I first got into development I looked to | people like Martin's for how I should write code. | | But I had more and more difficulty reconciling bizarrely | optimistic patterns with reality. This from the article perfectly | sums it up: | | > _Martin says that functions should not be large enough to hold | nested control structures (conditionals and loops); equivalently, | they should not be indented to more than two levels._ | | Back then as now I could not understand how one person can make | such confident and unambiguous statements about business logic | across the spectrum of use cases and applications. | | It's one thing to say how something should be written in ideal | circumstances, it's another to essentially say code is spaghetti | garbage because it doesn't precisely align to a very specific | dogma. | headbee wrote: | This is the point that I have the most trouble understanding in | critiques of Fowler, Bob, and all writers who write about | coding: in my reading, I had always assumed that they were | writing about the perfect-world ideal that needs to be balanced | with real-world situations. There's a certain level of bluster | and over-confidence required in that type of technical writing | that I understood to be a necessary evil in order to get points | across. After all, a book full of qualifications will fail to | inspire confidence in its own advice. | lostcolony wrote: | This is true only for people first coming to development. If | you're just starting your journey, you are likely looking for | quantifiable absolutes as to what is good and what isn't. | | After you're a bit more seasoned, I think qualified comments | are probably far more welcome than absolutes. | dnautics wrote: | > After all, a book full of qualifications will fail to | inspire confidence in its own advice. | | I don't think that's true at all. One of the old 'erlang | bibles' is "learn you some erlang" and it full of | qualifications titled "don't drink the kool-aid" (notably not | there in the haskell inspiration for the book). It does not | fail to inspire confidence to have qualifications scattered | throughout and to me it actually gives me MORE confidence | that the content is applicable and the tradeoffs are worth | it. | | https://learnyousomeerlang.com/introduction#about-this- | tutor... | lobstrosity420 wrote: | > it's another to essentially say code is spaghetti garbage | because it doesn't precisely align to a very specific dogma | | Does it say that though? | beckingz wrote: | All codebases are spaghetti garbage, but some are useful. | joshribakoff wrote: | Can you provide a source where he said that? Or did he actually | say something more like "thats when I consider refactoring it"? | | My recollection of the clean code book and Fowler books were | very much "I think these are smells, but smells in the code are | also fine" | | Note: Robert Martin and Martin Fowler are different people. Are | you saying Fowler said this? | strict9 wrote: | It's directly from the article, and Clean Code was one of the | first books I purchased. | | Fixed the typo, thanks. | joshribakoff wrote: | The article is not written by Robert Martin, so that | doesn't necessarily establish he said that. You also | implied Fowler said it. Thanks for clarifying. | | I believe Robert Martin did say this, but there was | probably a preface from the book that didn't make it into | the article, so the quote in the article may be a bit out | of context. | goto11 wrote: | The problem is not really with this book IMHO. Most of its advice | and guidelines are perfectly sensible, at least for its intended | domain. | | The problem is people applying principles dogmatically without | seeing the larger picture or considering the context purpose of | the rules in the first place. | | This book or _any_ book cannot be blamed for people applying the | advice blindly. But it is a pervasive problem in the industry. It | runs much deeper than any particular book. I suspect it has | something to do with how CS education typically happen, but I 'm | not sure. | p0nce wrote: | Disagree. There are zillions of worse books than Clean Code to | forget first. | ptx wrote: | > _Why are we using both int[] and ArrayList <Integer>? (Answer: | because replacing the int[] with a second ArrayList<Integer> | causes an out-of-bounds exception.)_ | | Isn't it because one is pre-allocated with a known size of n and | the other is grown dynamically? | | > _And what of thread safety?_ | | Indeed. If he had written the prime number class like the earlier | example, with public static methods creating a new instance for | each call and all the other methods being private instance | methods, this wouldn't be an issue. | oneepic wrote: | I have the same complaint with Code Complete. I read bits in | college and I'm not sure I follow most of its advice today (i.e. | putting constants on the left side of a comparison). | | However, the book also presents the psych study about people not | remembering more than 7 (+/- 2) things at a time (therefore you | should simplify your code so readers don't have to keep track of | too much stuff) and it stuck with me. I must be one of the people | with only 5 available slots in their brain... | | (edited for clarity) | tgv wrote: | > 7 (+/- 2) | | That study was done for specific stimuli (words, digits), and | doesn't generalize to e.g. statements. There are studies that | show that rate of presentation, complexity, and processing load | have an effect. However, STM capacity is obviously limited, so | it's good to keep that in mind when you're worried about | readability. And I think it's also safe to assume that expert | programmers can "chunk" more than novices, and have a lower | processing load. | dsego wrote: | > putting constants on the left side of a comparison | | Yoda conditions? I hate those, they are difficult to read. Yes | they are useful for languages which allow assignments in | conditionals, but even then it's not really worth it. It's a | very novice mistake to make. For me equality rarely appears in | conditionals, it's either a numeric comparison or checking for | existence. | Yajirobe wrote: | > output arguments are to be avoided in favour of return values | | what is an output argument? | [deleted] | hibbelig wrote: | An output argument is when you pass an argument to a function, | the function makes changes, and after returning you examine the | argument you passed to see what happened. | | Example: the caller could pass an empty list, and the method | adds items to the list. | | Why not return the list? Well, maybe the method computes more | things than just the list. | dsego wrote: | > Why not return the list? Well, maybe the method computes | more things than just the list. | | Or in C you want to allocate the list yourself in a | particular way and the method should not concern with doing | the allocation itself. And the return value is usually the | error status/code since C doesn't have exceptions. | augustk wrote: | An output argument (or parameter) is assigned a result. In | Pascal, for instance, a procedure like ReadInteger(n) would | assign the result to n. In C (which does not have variable | parameters) you need to pass the address of the argument, so | the function call is instead ReadInteger(&n). The example | function ReadInteger has a side effect so it is therefor | preferable to use an output parameter rather than to return a | result. | jacquesm wrote: | That's a C/C++ trick where a location to dump the output is | presented as an argument to the function. This makes functions | un-pure and leads to all kind of nastiness such as buffer | overruns and such if you are not very careful. | | sprintf(buffer,"formatstring", args) | | 'buffer' is an output argument. | [deleted] | pjc50 wrote: | Not only in C land; C# has "ref" (pass by reference, usually | implying you want to overwrite it) and "out" (like ref but | you _must_ set it in all code paths). Both are a bit of a | code smell and you're nearly always better off with tuples. | | Unfortunately in C land for all sorts of important system | APIs you have to use output arguments. | jlarocco wrote: | It's wrong to call output parameters a "C/C++ trick" because | the concept really has nothing to do with C, C++, buffer | overruns, purity, or "other nastiness". | | The idea is that the caller tells the function its calling | where to store results, rather than returning the results as | values. | | For example, Ada and Pascal both have 'out' parameters: | https://stackoverflow.com/questions/3003480/the-use-of-in- | out-in-ada#3004067 https://freepascal.org/docs- | html/ref/refsu66.html#x182-20600014.4.3 | http://www.ada-auth.org/standards/rm12_w_tc1/html/RM-6-1.html | | Theoretically, other than different calling syntax, there's | conceptually no difference between "out" parameters and | returning values. | | In practice, though, many languages (C, C++, Java, Python, | ...) support "out" parameters accidentally by passing | references to non-constant objects, and that's where things | get ugly. | applepple wrote: | This article is garbage. The argument is basically like saying | "famous scientist X was wrong about Y, let's stop doing science. | Clearly there is no point to it." | | I cannot believe what I am reading here. | | My open source community knows exactly what good code looks like | and we've delivered great products in very short timeframes | repeatedly and often beating our own expectations. | | These kinds of articles make me feel like I must have discovered | something revolutionary... But in reality I'm just following some | very simple principles which were invented by other people | several decades ago. | | Too many coders these days have been misled into all sorts of | goofy trends. Most coders don't know how to code. The vast | majority of the people who claim to be experts and who write | books about it don't know what they're talking about. That's the | real problem. The industry has been hijacked by people who simply | aren't wise or clever enough to be sharing any kind of complex | knowledge. There absolutely is such a thing as good code. | | I'm tired of hearing developers who have never read a single word | of Alan Kay (the father of OOP) tell everyone else how bad OOP is | and why FP is the answer. It's like watching someone drive a nail | straight into their own hand and then complain to everyone that | hammer and nails are not the right tool for attaching two pieces | of wood together... That instead, the answer is clearly to tie | them together with a small piece of string because nobody can get | hurt that way. | | Just read the manual written by the inventor of the tool. | | Alan Kay said "The Big Idea is Messaging"... Yet almost none of | the OOP code I read designs their components in such a way that | they're "communicating" together... Instead, all the components | try to use methods to micromanage each other's internal state... | Passing around ridiculously complex instances to each other | (clearly a whole object instance is not a message). | [deleted] | oriolid wrote: | > The argument is basically like saying "famous scientist X was | wrong about Y, let's stop doing science. Clearly there is no | point to it." | | In my opinion the argument is more "famous paper X by scientist | Y was wrong, let's stop citing it". Except that Clean Code | isn't science and doesn't pretend to be. | applepple wrote: | If the article only attacked that specific book "Clean Code", | then I would not be as critical. But the first line in the | article suggests that it is an attack against the entire idea | of writing good quality code: | | 'It may not be possible for us to ever reach empirical | definitions of "good code" or "clean code"' | | It might seem far fetched that someone might question the | benefits of writing high quality code (readable, composable, | maintainable, succinct, efficient...) but I've been in this | industry long enough (and worked for enough different kinds | of companies) to realize that there is an actual agenda to | push the industry in that direction. | | Some people in the corporate sphere really believe that the | best way to implement software is to brute force it by | throwing thousands of engineers at a giant ball of spaghetti | code then writing an even more gargantuan spaghetti ball of | tests to ensure that the monstrosity actually works. | | I see it as an immoral waste of human potential. | oriolid wrote: | > is an attack against the entire idea of writing good | quality code: > 'It may not be possible for us to ever | reach empirical definitions of "good code" or "clean code"' | | I read it as an attack against the idea that there are hard | and fast, objective and empirically verifiable rules for | good quality code. The Clean Code book itself is a perfect | example of how subjective such rules are. I feel really | sorry for you if the only methods for software development | that you know are "brute force it by throwing thousands of | engineers at a giant ball of spaghetti code" and sticking a | book that has some fanatic supporters. "Readable", | "maintainable", "succint" or "efficient" don't really | describe Martin's examples and many functional programming | enthusiasts would question "composable" too. Yes, I wasted | several hours of my life reading that book and I'm never | getting them back. | applepple wrote: | >> I feel really sorry for you if... | | I never said that this is what I believe. I said it's | what a lot of people in the corporate sphere believe. | It's the opposite of what I believe. | | OOP solves complex problems in a simple way. | | Functional Programming solves simple problems in a | complex way. | | Some concepts from FP are useful when applied within OOP, | but pure FP is simply not practical. It doesn't scale in | terms of code size, it's inefficient, it's inflexible, it | takes longer to develop and maintain, it's less readable | because it encourages devs to write long chains of logic | spread out across many files. FP's lack of emphasis on | important concepts such as blackboxing, high cohesion and | loose coupling encourages developers to produce poor | abstractions whose names sound highly technical but whose | responsibilities are vague and impossible to explain | without a long list of contrived statements which have | little in common with one another. | | Abstractions in FP tend to be all over the place. It | seems to encourage vague, unstructured thinking. | Decoupling state from logic makes it impossible to | produce abstractions which are high cohesion and loosely | coupled. It forces every component to mind every other | component's business. | | This is madness. If you don't care about structure, why | not just write the entire system as a single file and | define thousands of functions which call each other all | over the place? You would get the same spaghetti, but you | would save yourself the effort of having to jump around | all these files which don't add any meaningful structure | anyway. | sidcool wrote: | I am glad the author meant the book Clean Code and not the | concept. | valbaca wrote: | Here's my rant on Code Complete and Clean Code: | | I find that these two books are in many recommended lists, but I | found them entirely unforgettable, entirely too long, and without | any "meat." | | So much of the advice given is trivial things you'll just figure | out in the first few months of coding professionally. Code for | more than a week and you'll figure out how to name classes, how | to use variables, how scope works, etc. The code examples are | only in C++, Java, and Visual Basic (ha!). Completely ignoring | non-OO and dynamic languages. Some of the advice is just bad | (like prefixing global variables with g_) or incredibly outdated | (like, "avoid goto"? Thanks 1968!). | | Work on a single software project, or any problem ever, and | you'll know that you need to define the problem first. It's not | exactly sage advice. | | These are cherry-picked examples, but overall Code Complete | manages to be too large, go into too specific detail in some | areas, while giving vague advice in others. | | All books are written in a time and a few become timeless. | Software books have an especially short half-life. I think Code | Complete was a book Software Engineering needed in 2004, but has | since dropped in value. | | I will say, that Code Complete does have utility as a way to prop | up your monitor for increased ergonomics, which is something you | should never ignore. | | I have similar issues with Clean Code. One is better off just | googling "SOLID Principles" and then just programming using small | interfaces more often and use fewer subclasses. | | A better alternative is (from above) The Pragmatic Programmer | (2019), a good style guide, and/or get your code reviewed by | literally anyone. | grammarnazzzi wrote: | TLDR: Author recommends "A Philosophy of Software Design" over | "Clean Code" | devtul wrote: | I just bought the book, god damn it | gilbetron wrote: | It's an ok book to read and think about, but understand it is | written by someone that hasn't really built a lot of great | software, but rather is paid to consult and give out sage | advice that is difficult to verify. | | Read with great skepticism, but don't feel bad if you decide | not to read it at all. | aazaa wrote: | > First, the class name, SetupTeardownIncluder, is dreadful. It | is, at least, a noun phrase, as all class names should be. But | it's a nouned verb phrase, the strangled kind of class name you | invariably get when you're working in strictly object-oriented | code, where everything has to be a class, but sometimes the thing | you really need is just one simple gosh-danged function. | | Moving from Java as my only language to JavaScript and Rust, this | point was driven home in spades. A programming language can be | dysfunctional, causing its users to implement harmful practices. | | SetupDeardownIncluder is a good example of the kind of code you | get when there are no free-standing functions. It's also one path | on the slippery slope to FactoryFactoryManager code. | | The main problem is that the intent of the code isn't even clear. | Compare it with something you might write in Rust: | fn render(page_data: &PageData) -> String { // render | the page } | | If you saw that function at the top line of file, or if you saw | render.rs in a directory listing, you'd have a pretty good idea | of what's going on before you even dug into the code. | | Just randomly searching the Fitness repo, there's this: | // Copyright (C) 2003,2004,2005 by Object Mentor, Inc. All rights | reserved. // Released under the terms of the GNU General | Public License version 2 or later. package fitnesse.html; | public class HtmlPageFactory { public HtmlPage | newPage() { return new HtmlPage(); } | public String toString() { return | getClass().getName(); } } | | https://github.com/unclebob/fitnesse/blob/3bec390e6f8e9e3411... | | Really, how does this file even justify its existence? Its a | subsidy courtesy of language design decisions made a long time | ago. | hprotagonist wrote: | "Promote I/O to management (where it can't do any damage)" is the | actionably good thing i've taken from Brandon Rhoades' talk based | on this: https://www.youtube.com/watch?v=DJtef410XaM | | Living in a world where people regularly write single functions | that: 1. loads data from a hardcoded string path of file location | 2. does all the analysis inside the same loop that the file | content iteration happens in and 3. plots the results ... _that_ | cleavage plane is a meaningfully good one. | | The rest of the ideas fall into "all things in moderation, | including moderation", and can and should be special-cased | judiciously as long as you know what you're doing. But oh god | please can we stop writing _that_ function already. | avinassh wrote: | I have found other good articles on the same topic, by Hillel | Wayne author of Practical TLA+ | | Uncle Bob is ruining software - https://hillelwayne.com/post/10x/ | | Uncle Bob and Silver Bullets - | https://hillelwayne.com/post/uncle-bob/ | pelario wrote: | I'm surprised by the amount of detractors. We know from history | that any book with advice should not be taken too literal. | Reading the comments here, it feels almost like I read a | different book (about 10 years ago). | dsego wrote: | Read it again critically. Maybe you will see it differently, I | know I did when I read it a second time after a few more years | of experience. | rileymat2 wrote: | It is interesting that he uses a fitnesse example. | | Years ago we started using fitnesse at a place I was working, and | we needed something that was not included, I think it was being | able to make a table of http basic auth tests/requests. | | The code base seems large and complex at first, but I was able to | very quickly add this feature with minimal changes and was pretty | confident it was correct. Also, I had little experience in Java | at the time. All in all it was a pretty big success. | SketchySeaBeast wrote: | Fitnesse is Uncle Bob's baby so it makes sense to use that | example, he can't get through a book without talking about it | at length. | rileymat2 wrote: | Interesting, is probably the wrong, word. I should say | interesting to me, because I had a different experience with | it. And it was not any sort of theoretical analysis, it was a | feature I needed to get done. | ItsMonkk wrote: | This is an interesting article because as I was reading Martin's | suggestions I agreed with every single one of them. 5 lines of | code per function is ideal. Non-nested whenever possible. Don't | mix query/pure and commands/impure. Then I got to the code | examples and they were dreadful. Those member variables should be | readonly. | | Using Martin's suggestion with Functional Hexagonal Architecture | would lead to beautiful code. I know because that's what I've | been writing for the past 3 years. | tpoacher wrote: | Welcome to another episode of "X, as per my definition of X, is | bad - Let's talk about Y, which is another definition of X, but | not the one I disagree with". | markus_zhang wrote: | Maybe there is no such thing as clean code by following a set of | rules. I know the author of the book never advocateshis book as a | "bible" but it does give the reader such feeling. | | There is only years, decades of deep experience into certain | domains (e.g. game rendering engine programming, or, Customer | Relationship backends), extra hours reading proved high quality | code, countless times of reflection on existing code (that also | means extra hours reviewing existing code) based on the reading | and a strong will to improve them, not based on some set of | rules, but based on common sense on programming and many trials | of re-writing the code into another form. | | I think ultimately it goes down to something similar to | 10,000-hour rule: We need to put down a lot of time in X, and not | only that, we also need to challenge ourselves for every step. | rockbruno wrote: | One mistake I think people like the author make is treating these | books as some sort of bible that you must follow to the letter. | People who evangelised TDD were the worst offenders of this. "You | HAVE to do it like this, it's what the book says!" | | You're not supposed to take it literally for every project, these | are concepts that you need to adapt to your needs. In that sense | I think the book still holds up. | runevault wrote: | Uncle Bob himself acts like it is a bible, so if you buy into | the rest of his crap then you'll likely buy into that too. | | If treated as guidelines you are correct Clean Code is only eh | instead of garbage. But taken in the full context of how it is | presented/intended to be taken by the author it is damaging to | the industry. | BeetleB wrote: | I've read his blog and watched his videos. While his | _attitude_ comes off as evangelical, his actual advice is | very often "Do it when it makes sense", "There are | exceptions - use engineering judgment", etc. | | In no way is he treating his book as a bible. | mattmcknight wrote: | For me this maps so clearly to the Dreyfus model of skill | acquisition. Novices need strict rules to guide their behavior. | Experts are able to use intuition they have developed. When | something new comes along, everyone seems like a novice for a | little while. | | The Dreyfus model identifies 5 skill levels: | | Novice | | Wants to achieve a goal, and not particularly interested in | learning. Requires context free rules to follow. When something | unexpected happens will get stuck. | | Advanced Beginner | | Beginning to break away from fixed rules. Can accomplish tasks | on own, but still has difficulty troubleshooting. Wants | information fast. | | Competent | | Developed a conceptual model of task environment. Able to | troubleshoot. Beginning to solve novel problems. Seeks out and | solve problems. Shows initiative and resourcefulness. May still | have trouble determining which details to focus on when solving | a problem. | | Proficient | | Needs the big picture. Able to reflect on approach in order to | perform better next time. Learns from experience of others. | Applies maxims and patterns. | | Expert | | Primary source of knowledge and information in a field. | Constantly look for better ways of doing things. Write books | and articles and does the lecture circuit. Work from intuition. | Knows the difference between irrelevant and important details. | rendall wrote: | > _Primary source of knowledge and information in a field. | Constantly look for better ways of doing things. Write books | and articles and does the lecture circuit._ | | Meh. I'm probably being picky, but it doesn't surprise me | that a Thought Leader would put themselves and what they do | as Thought Leader in the Expert category. I see them more as | running along a parallel track. They write books and run | consulting companies and speak at conferences and create a | brand, and then there are those of us who get good at writing | code because we do it every day, year after year. Kind of | exactly the difference between sports commentators and | athletes. | chrisoverzero wrote: | I don't think that's picky at all. GP's characterization | appears to come from a book by Andy Hunt[1]. The two | creators (brothers, so both are Dreyfus) of the model don't | say anything of the sort[2]. | | [1]: https://moleseyhill.com/2009-08-27-dreyfus-model.html | | [2]: https://www.bumc.bu.edu/facdev- | medicine/files/2012/03/Dreyfu... | anarazel wrote: | The problem is that the book presents things that are at best | 60/40 issues as hard rules, which leads novices++ follow them | to the detriment of everything else. | [deleted] | throwaway2037 wrote: | The whole point of "You HAVE to do it like this, it's what the | book says!" is to sell more books or consulting. | | I agree: Clean Coders and TDDers are cut from the same cloth. | jugg1es wrote: | But that's what some people are doing - they take this book as | the programming bible. | lobstrosity420 wrote: | I understand that the people that follow Clean Code | religiously are annoying, but the author seems to be doing | the same thing in reverse: because some advice is nuanced or | doesn't apply all the time then we should stop recommending | the book and forget it altogether. | cloverich wrote: | Its not just that it doesn't always apply. Its that the | absracted rules are not useful as stand alone guides to | developing code, although they are presented as such. Its | the entire purpose of the book isn't it? The argument | against this book isn't that books about code style and | rules are bad. Its that this one is bad. And its often | recommended as core reading material to new developers | (several examples of that in this thread). I've read | several code style / guide books over the last decade. This | is one of the few I put down fairly early on because it | just didn't seem very good. | rockbruno wrote: | Sure, but it doesn't mean the book itself is bad. It's that | beginners should be aware that what's "right" differs from | project to project. | breck wrote: | Agreed. It's one of the best books on programming there is. | Like any book, probably 20% I don't agree with. But 80% of it | is gold. | LorenPechtel wrote: | Yup. I see the book as guide to a general goal, not a specific | objective that can be defined. To actually reach that goal is | sometimes completely impossible and in many other cases it | introduces too much complexity. | | However, in most cases heading towards that goal is a | beneficial thing--you just have to recognize when you're | getting too close and bogging down in complying with every | detail. | | I still consider it the best programming book I've ever read. ___________________________________________________________________ (page generated 2021-05-25 23:00 UTC)