[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)