[HN Gopher] Don't be clever
___________________________________________________________________
Don't be clever
Author : brentroose
Score : 179 points
Date : 2023-08-03 12:08 UTC (10 hours ago)
(HTM) web link (stitcher.io)
(TXT) w3m dump (stitcher.io)
| report-to-trees wrote:
| I think these stories and aversion to "clever" code end up
| hurting programmers overall. I wish we spent more time teaching
| how to choose the correct time and place to be clever instead.
| Searching for ways to make things better instead of mindlessly
| copy pasting how it was done by the person before me is how I
| learned my most valuable skills.
| ygouzerh wrote:
| [dead]
| Verdex wrote:
| It doesn't help that we don't have a good definition for what
| 'clever' means when referring to code.
|
| This article talks about a time someone made an abstract class
| and then took DRY a little too far. If this is an example of
| 'being clever' then anyone using map instead of writing a for-
| loop would have to be at gaussian levels of cleverness.
|
| [Also a class generator is implied to be less clever than an
| abstract class ... I'm not sure I can accept the usage of
| 'clever' in the article as meaningful.]
| _iostreamer_ wrote:
| I am not sure if teaching this skill would help or is possible.
|
| But I do agree with the direction of your thought. The reading
| material at least needs to be inherently balanced rather than
| writing itself getting balanced out by 50% of articles talking
| about "Using proxies to prevent null pointer errors"(guilty of
| writing this) and the other half being "Maybe proxies are a bad
| idea".
| PartiallyTyped wrote:
| I agree.
|
| A new pattern may seem "clever" or "difficult" if you haven't
| encountered it before; but if you spend time with it and work
| with it, it can look like the most natural thing in the world.
| AnimalMuppet wrote:
| Cleverness in the right place is good and useful. Cleverness in
| the wrong place is stupidity wearing nicer clothes.
| bqm wrote:
| Someone once told me: "optimize code for reading. In most
| companies, a piece of code is read 10x more than it is updated".
|
| Usually, when you do that, you end up with simple logic and
| potential repetitions over complicated abstractions.
| sweezyjeezy wrote:
| As a corollary, quite often in code there is a 'happy path', or
| small set of operations that people are going to be using 90%
| of the time. That code is going to get read even more, so make
| it as high quality and as simple as possible, don't mess with
| it unless you're very sure...
| slifin wrote:
| Composer/PHP never really supported proper autoloading of
| functions
|
| So everything is a class
|
| The majorities language's community has this super weird Java
| obsession The other half has a Rails obsession
|
| The best career decision I ever made was to watch the Rich Hickey
| talks
|
| 2x my wage in a few years and don't have to care about any of
| this obvious mis-match ever again
| bronxpockfabz wrote:
| I've being there (tm)
| joshstrange wrote:
| I agree that you should strive to not make "clever" code since it
| requires more cognition to parse later (and none of us have
| perfect memories). I try to strike a balance between reusable
| code that doesn't have 1 million flags/params to handle all the
| cases and writing straightforward code.
|
| It's easy for developers to look at 2 components and see the
| overlap and a lot of, normally junior, developers go overly-DRY
| and the readability and usability suffers for it.
|
| I'll take verbose over clever every day but at the same time I
| think this blog author goes a bit too far in assuming there was
| no use for a base class at all. I agree that the base class does
| not need to support every eventuality but a good base class would
| provide the base implementation with an easy way for subclasses
| to override the logic as needed. And no, tons of hooks probably
| aren't the way to do that either.
|
| We have some code where I work that I wrote as a base
| implementation of some CRUD logic but it requires your
| implementation to create stubs for get/put/delete/iterate/etc
| that call the internal logic. I got some pushback on this because
| "I have to write this boilerplate when I just want a straight
| passthrough" but I'd seen this play out enough times to hold my
| ground and, while I'm biased of course, I think it's proven
| successful in the long term. At the start you might just need to
| do a straight pass-through but business logic always changes or
| evolves and while on day 1 you might not appreciate having to
| write "boilerplate" you will be thanking yourself 1 month, 6
| months, 1 year down the line when you need to modfiy default
| flow. If a class never goes pass the "boilerplate" then great!
| But as soon as it does you'll be thankful for how easy it is to
| modify.
| xwowsersx wrote:
| Not disagreeing with the core assertion in this post because I
| think the author is correct that you can absolutely over-engineer
| things. But one thing I don't quite understand.
|
| > Some controllers had to do some things a little differently.
|
| ....
|
| > And young me? I just kept going. Adding the proverbial knobs
| and pulls to my abstract class
|
| Aren't the ways in which the concrete implementations vary
| supposed to be in the subclasses? One of the points of the
| abstract base class is to encapsulate common behavior. For things
| that differ, those belong in the concrete implementations. So
| _one_ of the mistakes here was to try to write a god class that
| could anticipate all of the ways in which the subclasses differ
| and to encode those into the abstract class in order to leave the
| subclasses as simple as having to specify the entity type.
|
| I guess that was the author's point ha?
| CaptArmchair wrote:
| I think the point of the author was that as a young - implied:
| inexperienced - developer he was blinded by the power of being
| able to describe the world in code; and inadvertently got stuck
| in the rabbit hole.
|
| Learning to take a step back and discern the best fitting
| abstractions that allow modeling the business domain is a
| learned skill. There's a difference between learning about
| object inheritance in school, and actually applying it in big
| projects where the effects only become apparent over the course
| of months.
| xwowsersx wrote:
| For sure. I meant to say that was _one_ of the points the
| author was making. You're absolutely right about what the
| core sentiment/point of the post was.
| eternityforest wrote:
| Everyone always wants simple beautiful code they can understand,
| I want boring code, that implements a beautiful UI users can
| understand.
|
| And if that means calling some black box of horrors mega library,
| that's fine as long as I don't have to look in the box.
|
| My first approach to embedded was an installer script that
| configures a stock distro image. Then I moved to a premade
| CustomPiOs image, but that's a nightmare and adds another large
| artifact to build and manage and maintain and isn't portable. Now
| I'm back to the installer script.
|
| Technical merits have nothing to do with the choice really.
| Nobody else was interested in learning anything about the
| customized image but they already know basic scripting.
|
| As much as I love high tech, and pretty much always use the
| megaproject latest hyper neophile stuff if I can.... I like not
| having to maintain custom stuff even more.
| at_a_remove wrote:
| I have a slightly different view and I think expressing it in
| front of the right people may have gotten me a job. Programmers
| ought to be clever, but clever is something you hold in reserve.
| Choose the boring, the staid, the obvious. Clever is for when
| you're backed into a corner by odd requirements, edge cases that
| the obvious will not cover, and so on. Then, you _comment your
| clever_ , journalism-style. Why were you clever? Where is the
| clever? Just how did you do it? When do you apply this? And so
| on.
| jasmer wrote:
| [dead]
| bellajbadr wrote:
| No body cared about the startup left with the junk code -_-
| ChrisMarshallNY wrote:
| As always, when making "one-size-fits-all" statements, my answer
| is "It depends."
|
| In many cases, I use inheritance and polymorphism, as well as
| extension, to make my code incredibly adaptable. It's basically
| habit.
|
| It is not always so easy to scrutinize (even by Yours Truly), but
| I document it well, and the structure can sometimes make tasks
| that might take days, take minutes (This has happened many times
| in the project I'm working on, now).
|
| A couple of weeks ago, one of the team needed a fairly large
| change to the operation of one of my screens. I was pretty
| reluctant, because it was one of those "Just one little change"
| things that actually translates to a massive codebase hit.
|
| However, when I reviewed the code (which I hadn't touched in a
| while), I found that I had left some fairly good hooks in the
| base class, so it actually took about five minutes to give them
| what they wanted.
| Sujeto wrote:
| I disagree. You could have done a better system. I think it's a
| must to be "clever" regarding code, especially if the repetition
| is huge.
| ravenstine wrote:
| I really dislike when someone considers my code "clever" because
| it always means they don't approve of it or think it's too
| confusing. No, I wasn't trying to be clever, but to create the
| most appropriate solution I could imagine.
|
| The author's problem isn't being overly clever, but that they had
| applied an inappropriate yet imtellectually-satisfying
| programming pattern that is notorious for being difficult to make
| exceptions for. The quote about the correct solution being a
| "class generator" shows that the author misdiagnosed their
| problem. If you continue down the path of making everything a
| class, you're gonna have a worse one than if you made most of
| your functionality into compostable functions, reserving classes
| for things that truly deserve it.
|
| So many issues in programming would be diminished if programmers
| toned down the thinginess of their code, focusing more on
| procedures and data shapes.
| Verdex wrote:
| Making things worse, I suspect there's at least two (and
| probably more) definitions for 'clever' that are thrown around
| and the intent half the time is that one definition is
| masquerading as the other.
|
| One plausible definition for clever code is that it's code that
| only works because of a non-obvious dependency on some other
| fact being true in the code base or outside of the code base.
| Once the fact is no longer true the code will be broken or
| subtly broken (or it will break or subtly break other code).
| "Ah ha, very clever, but we shouldn't do this."
|
| Another plausible definition is approximately, "I don't
| understand the feature or pattern you're using, and I don't
| want to understand it or form a coherent argument against it."
| robertlagrant wrote:
| The other option is "yes this works well and is very neat,
| but it's not going to withstand 5 years of people changing it
| and only reviewing the diffs."
| sfn42 wrote:
| Does anything withstand that? I don't think that's the
| responsibility of the original author, I think it is the
| responsibilities of all those devs and reviewers to make
| sure changes make sense and don't make the system worse.
|
| Nothing can stop morons from ruining good code. Good devs
| will evolve code into something that makes more sense.
| ravenstine wrote:
| The only code that probably does withstand it is code
| that does its job so thoroughly from its inception that
| there becomes no reason for someone to frequently make
| changes to it. I know such code exists because I
| definitely have run into code in popular repos that
| hasn't been touched in 10+ years. This is not exactly the
| norm though, particularly within web development, it
| seems. The more developers and reviewers you introduce to
| a unit of code, the more chaos you introduce, and said
| code is liable to losing comprehension until a major
| rewrite is performed.
| assbuttbuttass wrote:
| Agreed, when someone calls my code "complex" or "clever," it
| comes across as an insult
| ravenstine wrote:
| I wouldn't object so much to "complex" because there may be a
| much simpler way to writing my code. What I don't like about
| "clever" is that it's always a shit-sandwich that comes off
| as misrepresenting my intentions. I never write code with the
| intent of impressing myself or anyone else; my only interest
| is in writing minimal code that is maintainable and as easy
| to understand as possible. If my code doesn't achieve that,
| just tell me without patting me on the head by telling me I
| was trying to be clever.
| techbuttman wrote:
| I agree with you and am reminded of "KISS" (keep it simple,
| stupid): https://en.m.wikipedia.org/wiki/KISS_principle
| theshrike79 wrote:
| It's the focus on "minimal" that sometimes gets called
| "clever".
|
| Some that I've found in actual code
|
| Using ternary operators _everywhere_ instead of if-else
| just because it 's a few characters less, mixing them in
| the middle of already complex function calls so that it
| takes a less clever coder hours to untangle when there's an
| issue.
|
| Creating your own classes for storage, the best of which
| was a custom from-scratch implementation of a Vector, but
| it kept the 3 largest items in the first three cells. It
| was 100% API compatible with Vector and no part of the API
| gave any hint of the weird sorting mechanic.
|
| Fluent APIs. Just don't. They look pretty when written, but
| are a complete horror show to debug.
| mostlylurks wrote:
| > Using ternary operators everywhere instead of if-else
| just because it's a few characters less
|
| This isn't a fair assumption to make. One good reason to
| prefer ternaries over if-elses in languages which lack
| if-else expressions (and only have if-else statements) is
| that ternaries guarantee that you'll get a value for each
| branch, and with static typing you'll even know what type
| that value will be, whereas each branch of an if-else
| tree might do anything, is usually forced to rely on
| mutation to accomplish its job, and has no statically
| enforced guarantee of producing the value that you want.
|
| The fact that ternaries are terser is irrelevant. Some
| modern languages, like rust, have avoided copying this
| awkward if-else-ternary dichotomy by providing if-else
| expressions, which is the perfect solution to this
| particular situation.
|
| I am also honestly a bit concerned over how many
| developers consider ternaries hard to read. They're one
| of the most basic constructs of most of the popular
| languages out there, not some advanced corner case where
| it might be understandable to struggle with them.
| dgb23 wrote:
| > So many issues in programming would be diminished if
| programmers toned down the thinginess of their code, focusing
| more on procedures and data shapes.
|
| Exactly my reaction too. The code is complicated and requires
| boilerplate because it tries to adhere to a pattern that
| doesn't need to exist.
|
| Whenever something becomes entangled and complex, decompose it
| into pieces first (functions and data) and then put it together
| again, piece by piece. The right abstractions (if needed)
| emerge naturally from simple code.
| BoxFour wrote:
| The issue at hand is not about cleverness.
|
| Code was devised to address a specific problem (DRY), which it
| did quite elegantly. Then, the situation evolved, rendering the
| solution inappropriate. When a controller starts to deviate from
| the standard approach, it's best to refrain from inheriting from
| the base controller and instead create custom ones. Once all
| controllers operate independently, the base class can be safely
| removed.
|
| The key lesson here is not to avoid cleverness but to be aware of
| when the initial problem becomes obsolete.
| sjducb wrote:
| Experience will tell you that this happens often, and it's
| often better not to DRY in the first place.
| BoxFour wrote:
| If there is no process for re-evaluating code architecture
| choices, it can create challenges regardless of the approach
| taken.
|
| Experience tells me that relying on copy+paste as a solution
| isn't a cure-all, or even a better approach, and often
| introduces new problems in the future. A thoughtful and
| adaptive approach to code development is crucial to ensure
| long-term success and maintainability.
| GTP wrote:
| I never heard about class generators before, could someone please
| point me to some resources to learn more about it?
| karaterobot wrote:
| This describes our legacy codebase, which is quite cleverly
| optimized for the original business case our company had ~15
| years ago, and which is now a massive millstone dragging
| everything down into a black abyss. Just try to add a feature, I
| dare you.
|
| I would say: design for flexibility rather than cleverness in
| most cases.
| gnat wrote:
| Fifteen years is a long time for code to remain unchanged.
| Either you constantly work on it and change as the needs
| change, or you have a boom and bust cycle of productivity.
|
| Another way to look at it would be: holy shit, this company
| made it 15 years without investing in rearchitecting their
| codebase even as they took on new problems. That's amazing! Now
| we have 15 years worth of use cases to accommodate as we
| remodel/rearchitect/refactor/rewrite ... do you think that new
| codebase will last 15 years before it needs major work?
| imdsm wrote:
| "Brent" -- I had a chuckle at this unintentional coincidence
| burglins wrote:
| Oh no, the conclusion is so funny. That's the problem with "magic
| frameworks" like ASP.NET, where it's not obvious how to extend
| things if need be (if even possible).
| motoboi wrote:
| Code generations is just metaprogramming. The worst case, where
| you have to compile twice. The first one to generate the code and
| the second to compile the generated code.
| dclowd9901 wrote:
| > It's such a classic example, many of you probably recognise it.
| I wouldn't say my coding skills were bad, by the way.
|
| Oh, coding skills. This isn't about coding skills, this is about
| maintenance skills, which are wholly almost completely different
| than coding skills.
|
| Maintenance skills are about creating systems where you can:
|
| - extend, add or remove with low risk
|
| - quickly read and understand
|
| - quickly find a relevant bit
|
| - keep modules simple enough that you can consider all possible
| cases and build them robustly to handle those cases
|
| I see a lot in test code where engineers get overly clever and
| start writing looping test scripts and whatnot and pretty soon,
| you don't have a clear picture of what's even being tested
| anymore, or it becomes difficult to remove or change a part of
| the test.
|
| Testing is _not_ an area you want to get clever with. There is
| already plenty of built in complexity in introspecting and
| mocking runtime code to be adding a layer of cleverness to it.
|
| An engineer of a complex system doesn't strive to make things
| that are clever or novel. They strive to make things that are
| maintainable (as defined above). If in DRYing up code, or
| creating a solution, it's not making the code more maintainable,
| you're going the wrong way.
| tempodox wrote:
| Apparently the author hasn't learned the most important lesson:
| Think thrice before you use OO. And if you do, keep your
| hierarchies flat and simple. Prefer composition over inheritance.
| Make invalid states unrepresentable.
| robertlagrant wrote:
| Or don't use inheritance or automated copying. Use composition.
| brundolf wrote:
| I would frame this as:
|
| 1. Make sure there's always a trap-door where you can implement
| special cases
|
| 2. A good way to always have (or to create) a trap door is to
| compose out of smaller pieces; then if you need something to be
| different in one case you just "reassemble it" with pieces added,
| removed, or rearranged
|
| In the author's case, maybe CRUDController's key pillars of
| functionality could be broken out, still bundled together as
| CRUDController for normal cases, but then for special cases they
| could just assemble custom solutions out of those pieces instead
| of bending CRUDController into shape
| BossingAround wrote:
| To expand on the article from my personal experience, sometimes,
| there's a very thin line between "clever code" as in
| "overengineered", and "clever code" as in "I don't understand it,
| so I don't like it".
|
| Imagine situation where a team of Java developers need to create
| a component in Python. No problem!
|
| We come to the following code:
|
| ```
|
| list_of_resources = method_a()
|
| if not list_of_resources: handle_empty_list()
|
| ```
|
| Now, is the code above "clever", or not? As Java developers, you
| could get into arguments like "we should use
| `len(list_of_resources) == 0` instead" vs "it's not Pythonic and
| there's no reason to use len instead truth/falsey-ness".
|
| Of course, the whole problem gets exacerbated by unit testing,
| where `list_of_resources` can be a MagicMock, and therefore `if
| not list_of_resources` will never trigger.
|
| Of course, probably nobody will argue that the above snipped of
| code is overengineered. But, somehow, the same logic of "clever
| code bites you back" applies to more situations than just insane
| class hierarchies. And it's those very tiny fragments that seems
| so simple yet there are hidden issues that I find the most
| interesting to focus on.
| otikik wrote:
| > the whole problem gets exacerbated by unit testing, where
| `list_of_resources` can be a MagicMock, and therefore `if not
| list_of_resources` will never trigger
|
| Not a python or javascript programmer, but that alone already
| points at using `len` as a better (more testable) solution.
|
| In my limited understanding of python, there's no type-checker
| that will shout if someone in another team changes method_a()
| to sometimes return a `None` or `False` in addition to a List.
| So even if in python empty lists are falsy, the code will be a
| bit more robust if it doesn't rely on that feature to begin
| with. The code will error on the if instead of continuing and
| trying to use a `None` in a place where a list is expected
| later on.
|
| Disclaimer: I work with a language with only two falsy values
| (nil and false), so I might be biased
| chasd00 wrote:
| > you could get into arguments like "we should use
| `len(list_of_resources) == 0` instead" vs "it's not Pythonic
| and there's no reason to use len instead truth/falsey-ness".
|
| these kinds or arguments trigger me so badly i should probably
| talk to a therapist. I've been involved in a couple and it
| usually ends, rightly or wrongly, with me pulling rank and
| saying this is what it's going to be, this is what it's always
| going to be, move on to bigger things or gtfo of my team.
| contravariant wrote:
| This once more confirms my theory that using agent nouns as class
| names is a mistake.
|
| The author spent so much time thinking about what the
| CRUDController should be able to do they forgot to think about
| what it even _is_.
| bloopernova wrote:
| Yeah, like me (DevOps) writing our dev environment manager tool
| in Python when all the devs write nodejs. Then not being able to
| just ask them how to solve a problem in node, instead having to
| spend far longer figuring stuff out myself.
|
| Good for learning, bad for velocity. Although I do know a
| buttload more Python now.
|
| Yeah, clever! As my old man said, so sharp you cut yourself.
| rbreaves wrote:
| Tbh if your nodejs devs can't handle some Python then you need
| better devs. I'd been happy to help!
| bloopernova wrote:
| Some of them complain about regexes, and running commands in
| the terminal.
|
| Recently one was blocked for a day because they didn't think
| to google "how to make a script executable".
|
| Another one complained because their files weren't
| automatically copied to a new laptop. All on the macOS
| desktop directory of course.
|
| One of those wanted me to set up an Emacs config with org-
| mode, and write instructions on how to use it. Thankfully
| their manager "had words" with them and they gave up on their
| quest. They had never used Emacs before, and hated the
| command line.
|
| Oh and the enormous "fun" we have dealing with the staunchly
| windows/microsoft only developer who has to use a Mac. You'd
| think they were being asked to shoot children they way they
| carried on.
| jt2190 wrote:
| > Yes, I had failed to see the proper solution... It actually
| existed back then, I simply didn't know about it, no one told me
| about it, and I wasn't smart enough to question myself once I
| started going down a certain path.
|
| And there's your problem right there: Assuming that _nobody_ has
| _ever_ tried to solve this problem before so not bothering to
| look around at existing solutions.
|
| This is a failure of _lack of context_ , in two ways:
|
| 1. The author lacked knowledge about the various possible
| solutions and the strengths and weaknesses of each one.
|
| 2. The author lacked knowledge of the context into which the
| solution would be applied, e.g. a sophisticated solution might
| not make sense for an unsophisticated team to maintain.
| koromak wrote:
| Found solutions also tend to be super clever. Because they're
| packaged in a generalized library or white-paper, where the
| author doesn't have to worry about implementation details.
|
| Clever works great outside of your actual business code.
| lijok wrote:
| At a consulting gig I did, we were bootstrapping a brand new
| python engineering team for a new line of products. We chose the
| frameworks, set standards via decision records, wrote a template
| service that you would copy paste, and build on top. Cross
| cutting concerns were pulled out into a library that all of these
| services installed. Most things were standardized, all APIs felt
| like they were written by a single person, and yet little was
| abstracted. It was simple and beautiful. An engineer would come
| onboard, look at one repo and be able to navigate all service
| repos going forward. You could see and feel the repetition, aside
| for the business logic, it was like reading the same book over
| and over again. The team was delivering at a crazy velocity.
|
| Then a year or so later the client brings us back in to help
| diagnose and fix why their delivery dates continue slipping and
| number of bugs in production is increasing. Finds out a new, very
| vocal hire, came in, saw the repetition and decided to build a
| "service service". A service, into which you would pass a single
| python file of pure business logic encoded as a b64 string, with
| some decorators, and it would set up the endpoints,
| serialization, DB, etc automagically.
| sjducb wrote:
| If you feel confident refactoring some code then you have been
| able to read and understand it easily. If you can read and
| understand it easily then it's good code, leave it alone.
| IshKebab wrote:
| "Never refactor"? Great advice
| sjducb wrote:
| That's not what I said.
|
| If the code is confusing then absolutely refactor it.
|
| There's 3 levels of understanding DRY
|
| 1 programmer is unaware of DRY
|
| 2 programmer sees how much simpler code is when you reduce
| duplication
|
| 3 programmer sees how DRY can sometimes cause it's own
| problems.
|
| A good clue is that if the cyclomatic complexity of the
| system goes up when you DRY then you shouldn't DRY. For
| example if the new single method is now full of if
| statements then you're creating problems.
|
| Another clue is if you've just coupled things that
| shouldn't be coupled.
|
| Low cyclomatic complexity and coupling of components is
| much much more important than having a DRY code base.
| IshKebab wrote:
| From why you've said it sounds like maybe the issue was really
| _how_ they factored out the common code (Python loading base64
| encoded Python is clearly insane) rather than the fact that
| they did.
|
| It's difficult to draw conclusions from these sorts of things
| though because the answer is "sometimes you should copy and
| paste a bit, sometimes you shouldn't" and you just have to have
| skill and taste to know the difference.
| catchnear4321 wrote:
| that's the danger.
|
| you built a system for cogs.
|
| someone hired a thinker.
| lijok wrote:
| Very good point, eloquently worded
| meesles wrote:
| First time I've heard this, I love it. Definitely resonates
| both as the one who is there first, and then future ambitious
| folks would rather rebuild than understand and extend. Also,
| relatable when I come in somewhere and immediately start
| thinking about how I would re-build something instead of
| improving it. We sure have a lot of hubris in software, don't
| we?
| shrimp_emoji wrote:
| If you're looking at a giraffe's laryngeal nerve[0], you
| see it making a circuit from the jaw, down the loooong
| neck, to the chest, back up the loooong neck, and to the
| jaw again. It's like this because it used to be an small,
| innocent "u" shape back before giraffes evolved long necks.
| As their necks elongated, there was never any effort to
| rewire the nerve, so it elongated too.
|
| When encountering that situation, the correct urge is to
| rebuild against current circumstances.
|
| 0: https://forum.donanimhaber.com/store/c9/ef/2d/c9ef2d955e
| 3f99...
| citrusynapse wrote:
| "What I'm hearing is, there's a vacuum in the market for
| giraffe re-cabling services."
| lijok wrote:
| I hope you regularly give talks. You would be an amazing
| speaker.
| sfn42 wrote:
| Eh, in this case your "thinker" sounds kinda dumb to me.
| catchnear4321 wrote:
| we aren't disagreeing.
|
| thinking isn't always smart. being a cog isn't always dumb.
|
| sometimes the smartest thing to do is understand how a
| system is built and accept that until you understand it,
| you should learn about it. as a cog.
| PartiallyTyped wrote:
| There are cases where doing the "same" work twice is
| sufficient, preferred even...
|
| Somehow some colleagues found it absurd to parse some code
| twice, but don't see the insanity of parsing code, getting an
| AST, serializing, sending it, and then deserializing correctly.
|
| Never mind the fact that the serialized AST is 250x larger, and
| deserializing arbitrary constructs without codegen is a whole
| different can of worms, and especially harder when the code is
| running in 2 different programming languages.
| pachico wrote:
| I read this like "if I was was really clever back then I would do
| what now I think is clever" and my humble opinion about the
| ultimate solution for it really horrifies me (the class
| generator).
|
| Maybe I'm not clever enough. Maybe I don't hate copy and pasting
| enough.
| layer8 wrote:
| The problem isn't copying code, the problem is if the code has to
| evolve and then has to be changed in a dozen places consistently.
| Each place by now having its own particular knobs and special
| cases doesn't make it easier.
|
| In the context of code maintenance and evolution, there is a
| tension between duplication and abstraction. Both have their
| drawbacks. There is no silver bullet. One can overdo both.
| spion wrote:
| I have a softer rule for myself, which is "If you are going to be
| clever, make a (depends on the size of the abstraction): README,
| detailed API docs, possibly even a landing page. Specify
| behavior, write diagrams, explain what and why, document edge
| cases. Also, periodically have someone who's never seen it give
| honest feedback based on the docs only."
|
| If that sounds like too much work, then its not worth it.
| neontomo wrote:
| While I agree with the sentiment to keep things simple, I feel
| that saying "don't be clever" is just a way for developers to
| sound _actually_ clever. :-D
| firefoxd wrote:
| Right now, I'm working on two databases that almost do the same
| thing. One is legacy, the other is the new and improved. I can't
| delete the old one because there are several teams that generate
| their reports from it, I can't delete the new one because new
| functionality depends on it.
|
| When we add a new feature, we run update queries on both
| databases (but not always). We need several months of work and
| create training for everyone to move to the new DB. The business
| does not recognize the work as valuable so we continue to do this
| dance.
|
| How did we get here? Someone with great intentions thought "I
| hate this crappy mssql db, I'll create a new modern one". That
| person has left the company many years ago. This is now my real
| life example for "... now you have two problems"
| max_hammer wrote:
| If both database have same schema and data migrating reporting
| tools to new tool should not be a big challenge.
|
| Also, I would put other DB in keep the lights on (KTLO) and not
| provide any support or patches. You need to make your problem
| thier problem.
| Cutman wrote:
| The real hidden message in the article is: no matter how great
| you think your code is, when you revisit it a few years later, it
| will (nearly) always be crappy.
|
| The lesson is to keep it simple so everyone can recognize that
| it's crappy and help fix it/make it worse.
| log101 wrote:
| I did exactly the same thing lately, calendar had to be delayed 2
| weeks at the end, almost got fired .)
| vbo wrote:
| So much of the horribly unmaintainable code I've come across was
| the result of overengineering that whenever I see an abstraction
| I have a compulsive urge to scream into the void. Some (the
| simplest ones) are ok, but other times an abstraction that seems
| like a good idea on day 0 evolves into an unmanageable hodgepodge
| of hacks that makes an innocent developer that bumps into it
| years later question their choice of career.
| mcv wrote:
| I don't understand why he added all those exceptions to the
| abstract class. That's not what abstract classes are for. The
| initial code example shows an implementation class inheriting
| from the abstract class; that seems like the perfect place for
| exceptions. Why not simply use that most basic feature of OOP?
| Override the parts of your abstract class that you need to
| override.
| SanderNL wrote:
| I have swung both ways and I think I now settle somewhere near
| "boring is good" and "repetition is harmless (compared to the
| astronomic costs of wrong abstraction)".
|
| Especially repetition seems to be hated with the might of a
| thousand suns and while I get it, because I myself hated it, I
| now can see the beauty of it.
|
| What is _currently_ a superficial repetition - a bunch of
| endpoint handlers, some forms - will often turn out to be
| similar-looking instances of completely different problems.
| Repetition is quite often deeper than just "code looks the
| same".
|
| It takes a while to get to know these nuances.
| hliyan wrote:
| I've come to the same conclusion as you. Code is typically read
| more times than it is written/modified, so it is better to
| optimise for the common case, i.e. code that is tedious to
| write but simple to read and follow.
| jstimpfle wrote:
| I've seen my share of cases where the repetition was only
| because the original author didn't know how to abstract the
| problem say, using a simple struct. Or even easier, a simple
| function.
|
| I've sometimes taken on factoring the repeated code using
| common abstractions, and when that was done it often turned out
| that there was much more to factor out. And that the code
| quality could massively be improved because there are often
| some tricky situations that can only be properly handled when
| everything goes through a single place with all the necessary
| context.
|
| As an example, we had a config file with an ini-like structure
| that was parsed by some central parser code. So far, so good,
| but after the parse, the resulting key-value pairs were
| processed like this: if
| (string_is_equal(config_key, "foo_setting"))
| config->foo = parse_number(config_value); else if
| (string_is_equal(config_key, "bar_setting"))
| config->bar = parse_string(config_value);
|
| Repeating this pattern dozens of times is decidedly not cool.
| It lacks expression of the common structure, it imposes and
| prevents code optimizations and fixes (such as error handling).
|
| Sometimes I was told that abstraction isn't worth the hassle
| and boring and repetitive is good. By factoring the code
| allegedly we risk breaking it and make it harder to fix. Those
| people didn't see how broken and incomplete the code is
| precisely because there is a lack of abstraction. We can't even
| know that there isn't some "quux_setting" with broken handling
| code because of course there aren't proper tests for each
| config setting or combination of settings.
| plagiarist wrote:
| If it can be abstracted, often the abstraction can have unit
| tests. That's my goal, it is not just about avoiding
| boilerplate and repetition, it is that boilerplate and
| repetition are more surface area for errors.
| toshk wrote:
| Repetition can definitely cause big issues.
|
| Most of time you want business logic in the same place. In a
| big & old codebase it can be very hard to fix things if there
| are multiple places.
|
| But certain things can be easily repeated.
| sanderjd wrote:
| Always a continuum. Repetition is simple and good until it
| sucks :) Intuiting the right balance is an important part of
| the craft.
| Mezzie wrote:
| There are also valid use cases for preferring boring
| repetition.
|
| Most of the time when I do any kind of coding work, it's as a
| single person project for companies/orgs with absolutely zero
| tech people. For instance, the last project I took on was for a
| bra shop of 3 people. No IT. Nobody on staff who has any idea
| how anything tech works - the closest is the person who taught
| herself the basics of using Shopify + advertising platforms.
|
| Which means that in the future if they need somebody to look at
| or update anything I've done, I can't assume they're going to
| have access to a well-trained, talented person - it's equally
| likely they'd go "hey so and so's kid does 'computer stuff'
| let's see if they can fix it for us." Knowing that the next
| person who looks at my work might have very basic level skills
| leads me to prefer being repetitive and 'simple' over the more
| efficient solutions I can think of.
|
| It's the difference between a regular Wikipedia article and the
| Simple English Wikipedia - if I'm writing something that is
| likely to be maintained by beginners or apprentices, making it
| easy to understand and work with matters a lot more.
| wizofaus wrote:
| I hate repetition because it's nearly always laziness - it
| takes less thought/time to copy and paste a few lines of code
| than it does to factor them out into a reusable function and
| decide where to put it (and with what name). I'm taking about
| scenarios where the business logic needs to be exactly the same
| in both cases, there just happens to multiple ways to reach
| that point.
|
| On the other hand I also hate having to deal with shared
| functions that have been repeatedly adapted and extended to be
| able to deal with all the various edge cases to the point they
| have 20 cryptically named parameters and no reasonable way of
| guessing what the output should be for given set of inputs.
|
| But I'd still say more of my time is used up dealing with
| problems caused by lazy copying & pasting than by shared
| functions becoming overcomplicated or buried under excessive
| layers of abstraction.
| horsawlarway wrote:
| > I'm taking about scenarios where the business logic needs
| to be exactly the same in both cases, there just happens to
| multiple ways to reach that point.
|
| I've seen lots of these cases turn out to be "the business
| logic _happens_ to be exactly the same in both cases ".
|
| It might have been a single feature at one point where it
| should have been identical, but two flows going there in two
| different ways means it's serving two masters. Often it will
| diverge as the product grows and those flows become their own
| features with differing requirements.
|
| ---
|
| And also - "it takes less thought/time to copy and paste a
| few lines of code than it does to factor them out into a
| reusable function and decide where to put it (and with what
| name)."
|
| Yes, that's the point. It takes both less time and thought,
| and it often diverges anyways. You are wasting time and
| creating complexity.
| wizofaus wrote:
| But the divergence is more often than not unintended and
| causes inconsistent/ unexpected behaviour when a change is
| made in one copy and not the other, which is why it ends up
| using up more time in the end. And how is extracting a few
| lines of code into a function and calling that more
| complexity than having two identical copies of it?
| sfn42 wrote:
| If the function grows like you describe it's because the
| developers are doing bad work. Instead of extending the
| function into a monster it should be split appropriately
| according to the new requirements. In some cases it may end
| up being multiple classes and that's fine. What isn't fine is
| cramming multiple classes worth of complexity into one
| function just because it almost did what you needed.
| laserlight wrote:
| > If the function grows like you describe it's because the
| developers are doing bad work.
|
| Exactly. Evolving a nice function into one that accepts
| lots of arguments is a product of the same mindset that
| copy-pastes code.
| wizofaus wrote:
| Indeed...laziness in both cases! I certainly admit I've
| been guilty of "ooh this function already exists to do
| this, except in this case I need to tweak it slighty, so
| I'll just add another parameter" - which is usually OK
| the first or second time: the issue is when it sets in
| motion a pattern of behaviour that other devs keep
| following without stopping to question "has this function
| grown too complicated", or worse "I know this needs
| refactoring, but we need this bug fix in now, I'll create
| a tech debt ticket and come back to it later", but of
| course never do.
| sanderjd wrote:
| Laziness is good though. If repetition requires less work for
| the same outcome, that's good. If abstraction or automation
| of some kind (like codegen) requires less work, then that's
| good.
|
| But the question is, "less work over what time scale?".
| Repetition usually requires less work over short time scales
| but often requires more work over longer ones. But not
| always! I see people abstracting and automating things in
| throwaway scripts, tools, and PoCs. That is a waste of time.
|
| There is a series of xkcd comics about this, which are all
| spot on: https://xkcd.com/974/, https://xkcd.com/1319/,
| https://xkcd.com/1205/.
|
| I refer back to that table in 1205 pretty often :)
| wizofaus wrote:
| > Repetition usually requires less work over short time
| scales but often requires more work over longer ones
|
| Yes, that's been exactly my experience, and not by a small
| amount either.
| Pxtl wrote:
| My rules is never copy paste. If it's worth repeating, it's
| worth typing.
| lopatin wrote:
| I feel the same way. Repetition is fine when you're working
| within a solid, well established framework. If you're writing
| 100s of GET endpoints, and most of them requires you to prefix
| your endpoints withs some @GET and @RequriesToken decorators,
| so be it.
|
| If there are other forms of repetition within the user-code,
| there are ways of dealing with it. But writing a super class to
| solve superficial code repetition is most often the wrong way
| to go.
| _fat_santa wrote:
| I have what I call the "10 second rule". The rule is that an
| experienced programmer (ie. someone who has written the type of
| code your codebase is written in, whether Python, JS, etc)
| should be able to look at a code snippet, any code snippet in
| your code, and figure out what it does in about 10 seconds.
| There are obviously exceptions to this where complexity can't
| be avoided but overall I found the tradeoff is worth it
| especially on a team of multiple devs.
|
| I currently work for a client that has a very very large react
| native app that is pretty hard to wrap your head around. I
| can't tell you how many millions of dollars they have wasted in
| dev hours because it takes a dev way to long to even figure out
| what the code is doing before they can start making changes.
| duderific wrote:
| I always try to imagine that I'm coding for other people
| rather than coding for myself. This helps me to remember to
| make the code as easy to understand as possible.
| JKCalhoun wrote:
| Mine is the "10th grade rule".
|
| A 10th grader should be able to understand it.
| jprete wrote:
| I thought about this a while and concluded that the right
| question is whether the repetitions are intended to do the same
| thing. If they are, deduplicate them. If not, leave them alone
| to evolve independently.
| nperez wrote:
| I once worked in a role where my boss made me rework commits that
| were too DRY. I had to completely decouple features that were
| early in development, then reopen the PR. I thought it was
| excessive and pointless at the time, but I've seen enough stories
| like these to get it now.
| charles_f wrote:
| This sounds to me more like an argument for composition over
| inheritance(1) than a rebuke of being clever.
|
| That your code is complex to change _is_ a good sign that it 's
| not design in an appropriate way.
|
| Switching from "this code is repetitive" to "therefore I will
| automate the production of this code" sounds unfortunate though.
|
| 1: https://en.m.wikipedia.org/wiki/Composition_over_inheritance
| jbandela1 wrote:
| > Yes, I had failed to see the proper solution: a class generator
| -- so that I didn't have to manually copy code again
|
| No, please don't. This is jumping from the frying pan into the
| fire. If you think abstract base classes can be clever and hard
| to understand, code generators can be even more so. In addition,
| because code generators are a one way conversion, and the
| generated code evolves independently and the code generator
| itself is evolving independently, you end up with a bunch of
| things that are subtly different yet somewhat related in a way
| that you will need an advanced degree in evolutionary biology to
| understand.
| OhMeadhbh wrote:
| What's wrong with macros? I use them every day. They're fine.
| slim wrote:
| have you heard about Jhipster ? it's a code generator "written"
| in javacript. It generates Java/Spring for the backend and
| Angular for the front end
| jader201 wrote:
| My understanding is that in this case, the code generator is
| merely a boilerplate generator that isn't meant to keep the
| code in sync, but just do the initial copy/paste work.
|
| I think code generators are a perfectly acceptable solution in
| cases like this, when the starting point all looks the same,
| and it needs to diverge from there. Especially if the
| generation logic is fairly straightforward.
|
| There are a lot of IDEs that have boilerplate generators out of
| the box.
| sodapopcan wrote:
| Yes, this is what the author meant, no doubt.
|
| To be clear I'm not being sarcastic. Many frameworks have
| code generators that writes boilerplate for you.
| Pxtl wrote:
| If scaffolding is generating more than an SMS worth of code
| per file, it will drive you crazy because you still _want_ to
| keep it in sync.
| paulddraper wrote:
| Correct.
|
| The number of comments which latch onto "code generator"
| without understanding it are disappointing.
| xyzzy4747 wrote:
| This is also something the Rust language does well with
| macros. It's a pretty standard approach.
| fuzzy2 wrote:
| This is sometimes also called scaffolding, which I think is a
| better term. Code generation often means (compile-time or
| otherwise) generation of code from something else (like
| .proto definitions). Code that is not supposed to be modified
| by the developer and will be overwritten automatically.
| jader201 wrote:
| I've also seen "scaffolding" used to generate code that
| shouldn't be manually modified.
|
| E.g.: https://learn.microsoft.com/en-us/ef/core/managing-
| schemas/s...
| hgsgm wrote:
| That's someone who doesn't know what words mean.
|
| When a building is built, the scaffolding isn't an
| immutable part of the final product.
| tomjakubowski wrote:
| Well if we're being anal about the metaphor, in
| construction and renovation, the scaffolding eventually
| goes away. Scaffolded code rarely disappears entirely;
| some of it usually sticks around.
| abraae wrote:
| To be perhaps even more anal, after construction
| scaffolding is taken down, stored, transported to a new
| project and then used again.
| paulddraper wrote:
| Then that makes it an inappropriate word for both uses.
| fuzzy2 wrote:
| It's a little ambiguous with Entity Framework. There is
| no rule that says you cannot change the resulting code.
| If you want to continue with Code First, that is a
| totally valid approach, there's even a subsection on
| that.
| wallmountedtv wrote:
| As a blog post with the title "Don't be clever" ending up with
| the author's answer being an even clever-er solution, than
| talking about the hidden fight against complexity in software,
| was an odd ending note.
| chomp wrote:
| It's not a clever-er solution? As an example, `php artisan
| make:controller` and potentially some custom derivatives is
| going to solve like 95% of what he was trying to abstract.
| HappySweeney wrote:
| Until something goes wrong, and you didn't write it. At
| that point, have fun.
| dehrmann wrote:
| I didn't find either all that clever. It was premature
| abstraction, followed by abstraction-lite.
| lopatin wrote:
| To me the most ironic part was calling CRUDController clever
| in the first place. There could have been clever solutions to
| reduce the code repetition, but using inheritance to force a
| naive abstraction on every future programmer isn't it.
| cogman10 wrote:
| "Don't be clever, now let me tell you about the non-clever
| solution of writing a bespoke meta programming language to
| solve the problem of copy/paste"
| dietr1ch wrote:
| And with this inconsistency the author really pushed you
| into rejecting complexity.
|
| Maybe by looking like a fool he fooled us all?
| robomc wrote:
| Yeah surely a better approach is... rather than inheriting from
| an all powerful class, just compose controllers out of modules.
| Some used in almost every controller. Some pretty specific to
| one or two edge cases.
|
| Like the problem was having an all-or-nothing inheritance model
| for shared functionality, which also ended up very long and
| intimidating.
|
| I also don't really understand why emerging edge cases caused
| him to complicate his 90%-perfect parent class instead of
| just... overriding specific functions in the children. (The
| specifics of these overrides will probably make you want to
| refactor your parent class so that the overrides have clean
| joints but you shouldn't just dump the new child-specific logic
| into your parent)
| Pxtl wrote:
| Code generators can work _if_ you .gitignore the target
| folders, so everything generated must be used as-is or
| augmented by a _separate_ handwritten file like a subclass. And
| to integrate them tightly into your build process so the
| generator tool runs transparently.
|
| But even then they're a nightmare because your errors don't
| match your source files.
|
| "generate and edit" is suicide. "generate and treat the outputs
| as intermediate compilation objects" is merely an incredibly
| painful tool of last resort.
| jauntywundrkind wrote:
| This whole article is about whether to do or not, and I dig the
| lesson here. The author built on monstrosity they hate, and
| they propose perhaps creating another. How bold & brave!
|
| Of course lots and lots of people love telling us why not to do
| things. This is the cosmic-brain point of Steve Yegge's "Notes
| from the Mystery Machine Bus", of what we let guide us. Is it
| trying and hope and doing? When and where do we allow
| reservation in? https://gist.github.com/cornchz/3313150
| baz00 wrote:
| The funny thing is the last thing I did code generation for
| just generated classes which inherited abstract base classes.
| Then we had to inherit the generated code to extend it. It
| massively increased the complexity.
|
| Then someone came along and invented partial classes in C#
| which made this problem go away. Well it would if anyone wanted
| to do all the maintenance legwork which they didn't so half of
| it's a 5 class inheritance tree, some of it's partial classes
| and someone got really fed up with this shit and just
| arbitrarily stuffed Dapper in there one afternoon.
| aldanor wrote:
| I've read that paragraph with a /s at the end of it.
| eternityforest wrote:
| Is that what he's talking about? I thought he was talking about
| some pattern using first class classes rather than ever
| programmatically touching source?
| dimmke wrote:
| So much about programming in a larger sense is just getting
| abstractions correct. Too loose and they don't
| standardize/remove enough boilerplate. Too strict and they
| break or multiply when changes are needed.
|
| I am curious, since my own backend experience is limited
| (obviously there will be many opinions on this) but it seems to
| me like his mistake was using the inheritance of classes.
|
| If he had simply had a CRUD layer - that only standardized the
| actions of creating, removing, updating, or deleting a record
| (or records) from a database. This would accomplish
| standardizing the payload, as well as possibly doing an
| RBAC/Authorization check.
|
| He could have just written individual controllers that invoke
| that layer with custom business logic. He would even have more
| space to add comments indicating why the business logic is
| implemented this way. When someone needed to make a change,
| they could just go straight to the specific controller.
|
| Putting the impracticality of having to override specific
| methods aside, isn't it also a waste to instantiate a class
| full of methods that essentially do the same thing when you can
| just point to existing ones? Well I guess it is pointing to
| existing ones via inheritance, but it seems harder to reason
| about. But this is coming from someone who doesn't write
| traditional object oriented code very often.
|
| Genuinely curious to hear from backend people about this, it's
| not my area of expertise but I've learned a lot this year.
| cogman10 wrote:
| In my view, there's duplication that could use an abstraction
| and duplication that is merely coincidental. (See The Wrong
| Abstraction by Sandi Metz [1])
|
| Unless something screams "this should be the single source of
| truth about this" forget abstracting all together and just
| copy and move on.
|
| The problem with trying to create 1 "CRUD controller" is that
| there are always going to be hairy things that make parts one
| offs. Perhaps someone needs to add location headers because
| the underlying calculation takes too long. Perhaps they want
| custom status codes when things go wrong. Maybe they need to
| use web sockets or server sent events. As soon as any sort of
| needed customization comes into play you start finding
| yourself closer and closer to the framework you are likely
| using until you reach a point of "Why am I trying to wrap the
| entire framework? Why can't I use it directly?"
|
| And if you've made the mistake of pulling that abstraction
| into a library, heaven help you when you need to update
| things. What happens if the underlying framework library
| makes a breaking change? Or if you need to make a breaking
| change to support some feature? It all gets really messy
| really fast and now instead of just impacting the 1
| application you are impacting 100.
|
| Updating shared code is never as easy as you might think.
|
| But, on the flip side, I can't think of anything easier, even
| if it's mostly boiler plate, than writing a controller that
| calls some business logic that works with a DB. Regardless
| the language or framework. The hard part of such applications
| is always the business logic and not the actual controller
| wiring.
|
| [1] https://sandimetz.com/blog/2016/1/20/the-wrong-
| abstraction
| dimmke wrote:
| Thank you for that link, I've never heard that but it's
| definitely going to be one of the new things I meditate on
| quite a bit. Especially combined with the concept of all
| "all abstractions are leaky"
|
| If you're designing a backend service that has multiple
| "heads" - say, a web application and a mobile application.
| Then it makes sense that service should be code that
| manipulates the database(s) via business logic, and at the
| very least should be in one place.
|
| But broadly, programmers do see value in abstracting
| interacting with the DB at least somewhat. It's why ORMs
| exist.
|
| I don't know if there's a real answer. An abstraction can
| be right for a while and then become wrong when you add a
| new requirement right? So is it pointless to use
| abstractions at all? That definitely feels like the wrong
| takeaway. I guess instead, it's don't immediately abstract
| out anything that feels abstractable when writing new code
| and don't feel obliged to use existing abstractions in a
| codebase until you're sure they fit what you're doing.
| cogman10 wrote:
| > I don't know if there's a real answer. An abstraction
| can be right for a while and then become wrong when you
| add a new requirement right? So is it pointless to use
| abstractions at all? That definitely feels like the wrong
| takeaway. I guess instead, it's don't immediately
| abstract out anything that feels abstractable when
| writing new code and don't feel obliged to use existing
| abstractions in a codebase until you're sure they fit
| what you're doing.
|
| The real answer is that, as much as some would like it to
| be otherwise, there aren't hard and fast rules in
| programming. Determining when it makes sense to abstract
| and when it doesn't is ultimately something that will be
| guided by experience.
|
| That said, DRY is dangerous. It's to easy to blindly
| follow and has disastrous effects when the wrong
| abstractions get made. It's far better to duplicate first
| and DRY when it becomes a pain.
| justincredible wrote:
| [dead]
| summerlight wrote:
| One good thing with a code generation approach is that we can
| actually inspect into the generated code and see what's going
| to happen in a relatively easier way. And it's usually working
| nicely with static analysis. For abstract base class or
| whatever, it's kind of hard to do.
|
| Of course, code generator (or even compiler) is harder to
| maintain, especially when you want the output to be human
| readable. So it's always about trade-off. Think about how many
| lines of code it's going to generate. If it's an order of 100k
| then it's usually worth the cost. 10k might be good to go. 1k,
| probably not.
| jonhohle wrote:
| IMHO, I don't think there are enough code generators, however,
| a good code generator's output should be a library, not
| editable source. I've had a lot of good experience using IDLs
| that could generate libraries for multiple languages that other
| code could be used with (e.g. define your REST url scheme and
| supported operations and a base class that gets generated that
| allows filling in logic).
|
| At least in Java world, annotation seemed to have taken over,
| but the mixture of code and interface definition kicks you into
| the language and allows too much flexibility that really make
| it no different than writing registration code. Having a nice
| declarative language without business logic sneaking in.
| moffkalast wrote:
| To paraphrase that old joke "I had a problem and I used a class
| generator, now I have a problemFactory".
| jackcviers3 wrote:
| "Write code that writes code" -- Tip 29, The Pragmatic
| Programmer[1]
|
| I think we need to be a little more nuanced, here.
|
| Don't be clever in your code generation, to be sure. But let's
| not act like you need advanced degrees for all meta-
| programming.
|
| What do you think phased compilers do? They generally transform
| the AST produced by your code into expanded versions or
| rewrites of that AST that are more amenable to machine
| interpretation or transformation into assembly.
|
| In a very real sense, nearly all production code goes through
| some kind of codegen process. It isn't clever, it isn't new, it
| generally isn't complicated (if this then that else that else
| ... when you get down to it). It is repeatable and reproducible
| and reliable enough method that the whole economic system of
| software is built on top of it. Most of the problems come with
| recursive applications of codegen and strange interactions
| between different subsystems of codegen.
|
| Here's a tip: Don't do recursive codegen in your user land
| metaprogramming.
|
| User code is _easier_, simply because it isn't as reusable as
| the language primitives themselves. But if you understand a
| problem well enough to essentially reduce it to copy, paste,
| and replace one or two items, codegen is superior because a bug
| fix during maintenance need only happen in a single place.
|
| My primary language recently added generic derivation to the
| language itself (as opposed to in macro libraries) and it is
| totally worth it as I almost never have to write a ser/de by
| hand. I never have to test the ser/de round trip. I don't have
| to pepper my code with second-class annotation primitives or
| keywords. I never misspell `asJson`, etc.
|
| Not everything should be done with codegen in user land code
| (code outside the compiler and language tooling). But if I see
| thousands of lines of json/avro/protobuf/test example
| copypasta, that's a code smell that needs to be addressed or it
| will lead to production bugs and take hours instead of minutes
| to fix.
|
| And when you write these things, you come to find out that they
| are just like any other data transformation task. They turn
| `A`s into `B`s, systematically.
|
| My mentor, when I became a professional coder, taught me that
| if you do something more than three times, its probably worth
| taking the time to generalize it and do it once before you push
| the changes into the version repository. It's been 18 years now
| since I began my career, and that simple principle has rarely
| steered me wrong across probably a dozen languages that I've
| written production code in.
|
| 1.
| https://www.goodreads.com/book/show/4099.The_Pragmatic_Progr...
| sanderjd wrote:
| Generating code is fine, if the generated code strictly never
| evolves independently of what it is generated from. For
| instance generating libraries from .proto files (or other
| declarative schema definition solutions) works really well. If
| the schema changes, you throw away the old generated code and
| generate brand new code, no problem.
|
| But if you want to make even a single tiny modification to one
| of the generated files, you're busted, you need a different
| solution.
| pphysch wrote:
| > But if you want to make even a single tiny modification to
| one of the generated files, you're busted, you need a
| different solution.
|
| Not totally true, if you can robustly express your tiny
| change as a `sed` or `awk` script, you can just append to the
| generator pipeline. Speaking from experience, do not condone,
| etc.
| dmoy wrote:
| I think GP means "make a tiny change [after generation,
| outside of the generator, and persist that change
| independent of the generator code]", which is where all the
| demons are waiting
|
| Modifying the generator itself to do something different
| every time, and doing GP's stated "regenerate and throw
| away the old stuff" is in line
| pphysch wrote:
| It's not modifying the generator. The generator may be a
| proprietary black box. It's wrapping the generator in a
| bash script that pipes the result through AWK, etc.
| xamolxix wrote:
| > It's wrapping the generator in a bash script that pipes
| the result through AWK, etc.
|
| Which is itself a generator
| dmoy wrote:
| Sure, that's orthogonal. If you wrap the generator in
| your build system and still always regenerate, it's
| effectively the same. And also, I think, not what GP was
| talking about
| pphysch wrote:
| Pedantic. There's a world of difference between grokking
| a new code generation DSL+codebase and a shell one-liner
| that fixes a string that is obviously invalid.
|
| Since the issue is the maintenance of such systems, it is
| absolutely relevant.
| mcv wrote:
| Generated code is fine if it's newly generated on every
| build. If you're going to have to maintain the generated
| code, it's not generated code anymore, but duplicated code.
| rubicks wrote:
| Seconded. How many in this thread have found generated code
| in source control? My trophy case includes artifacts
| produced by: flex, bison, gperf, swig, and one particularly
| nasty CORBA stub generator.
| lanstin wrote:
| No Perl?
| raincole wrote:
| Yes and the original article is about how duplicated code
| is ok. The discussion finally went the full circle.
| mcv wrote:
| The original article isn't very convincing though. I
| mean, I fully believe the single abstract super
| controller was a bad idea, but there are far better
| options than that and duplicate code. He's just comparing
| two of the worst ways to do it.
| ilyt wrote:
| > For instance generating libraries from .proto files (or
| other declarative schema definition solutions) works really
| well.
|
| ...does it ? Generated ones always feel being mismatched with
| the language paradigms. Maybe that's just my nightmares of
| dealing with MS Graph generated vomit hose of a library...
| lolsal wrote:
| The only time being clever has helped me has been putting out a
| fire in production, to stop the bleeding, and to buy us time for
| implementing a better fix in the next release.
|
| Being clever during feature development is a red flag to me. I'm
| a huge fan of the YAGNI principle.
| 0x4164 wrote:
| Yeah, dont be just clever. Be clever, practical, agile, KISS and
| sarcastic at the same time :).
|
| Be DRY enough. DRY what needed only, you will keep debugging
| easy.
| theshrike79 wrote:
| If the amount of time it takes to type your code is the biggest
| bottleneck in your project and you need to optimise for amount of
| characters written you're either the most genius 100x coder ever
| or ... not one of the best.
|
| You should always optimise your (work) code for readability, even
| if it takes longer to type. I've spent way too many hundreds of
| hours of my 25 year career untangling overly clever code just to
| figure out what's wrong.
|
| If you can't be sure the person who has to open up your clever
| code at 3AM to fix a live production issue can instantly see
| what's going on with the code, write more readable code.
| Preferably ask someone to review the pull request and see if they
| can understand what's going on.
|
| You can be as clever and terse you want on your own time, but on
| company time write readable code even if it's verbose.
| FreshStart wrote:
| ... otherwise you become unmanageable and subvert the reality
| assumptions of the ruling caste.
| dmichulke wrote:
| > ... abstract class that integrated with ... the ORM that I was
| using back then
|
| > Yes, I had failed to see the proper solution: a class generator
|
| Looks to me like the problem is actually object orientation
| [deleted]
| turnsout wrote:
| It seems like every coder needs to learn this the hard way at
| some point. "I'll save so much time!"
| foobarian wrote:
| I don't think this particular person would have learned the
| lesson.
|
| > Yes, I had failed to see the proper solution: a class
| generator -- so that I didn't have to manually copy code again.
| It actually existed back then, I simply didn't know about it,
| no one told me about it, and I wasn't smart enough to question
| myself once I started going down a certain path.
|
| I shudder for the next gig where they get to apply ^
| misja111 wrote:
| I think this is because most literature focusses on exactly
| those things. Design patterns to be one step ahead of some
| obscure future usage of your API, FP patterns to abstract your
| API into a 'something' that you could not just use to aggregate
| customer account balances in the database but, say, merge
| arbitrary lists as well etc.
|
| It's rare to find literature that tells you -not- to use new
| frameworks or design patterns, and if it exists, if probably
| doesn't sell very well because it's just not sexy.
| turnsout wrote:
| Yeah, you've hit the nail on the head--no one got rich
| selling a book called "There Is No Magic Bullet: Everything
| Sort of Depends"
| reidjs wrote:
| Now I'm tempted to write that book, maybe it could work.
| Find examples of things people desire and write the simple
| 'common knowledge' rules to achieve it, acknowledging the
| role of bad/good luck.
|
| Want to be healthy, in good shape? Get lots of sleep, eat
| well, exercise. Unless you suddenly get cancer.
|
| Want to be rich? Work a steady, boring job that pays well
| and live below your means. Unless a financial disaster
| occurs and you lose all that money you saved up.
|
| etc.
| turnsout wrote:
| Honestly I think that would be a really interesting book!
| It could be case studies of people who were 100% invested
| in a "system," but found that they got just as good (or
| better) results from following common sense guidelines
| and not sticking to them too rigidly.
| mostlylurks wrote:
| People often learn the wrong lessons from literature. Design
| patterns, for instance, are something you should learn, not
| as tools that you should eagerly find applications for, but
| rather as a shared understanding of the kinds of patterns
| that commonly occur in code, such that you have the right
| vocabulary to communicate about them with others, and such
| that you can easily recognize them when they occur in the
| code. You'll almost inevitably utilize various design
| patterns extensively in your code even if you've never heard
| of "design patterns", learning about them simply lets you
| understand what's happening.
| DaanDL wrote:
| Ah yes, the CrudController to solve all your problems. Also in
| this list: the AbstractBaseRepository
| valenterry wrote:
| Jajajaja, we know - "clever" doesn't mean "smart" anymore but
| "overcomplicated".
|
| Nothing to see here, please move on...
| WesolyKubeczek wrote:
| Which is why I always use the term ,,clever by half". Simply
| clever is actually good and a term of praise. Clever until an
| unforeseen (but foreseeable!) consequence bites back, is, well,
| not clever enough.
| otikik wrote:
| Your code shouldn't be "smart", most of the time anyway.
| Definitely not for a CRUD web app.
| erikpukinskis wrote:
| I've been working with Prisma and React Query recently and I
| wonder if those libraries haven't fallen victim to the same trap.
| They seem to want to "do everything you could possibly want to
| do, in a clean wrapper".
|
| But I wonder if they are trying to wrap domains that are too big
| to wrap, and should just be left to a programming language.
| Jun8 wrote:
| On a similar vein, Kernighan's Law: Debugging is twice as hard as
| writing the code in the first place. Therefore, if you write the
| code as cleverly as possible, you are, by definition, not smart
| enough to debug it.
|
| Discussed on HN a few years ago:
| https://news.ycombinator.com/item?id=22348386
| bob1029 wrote:
| My first lesson around this was in manufacturing. It has stuck
| with me much more strongly than any sort of software industry
| jargon. It is an incredibly simple phrase too: "Value-add".
|
| I just stay focused on that item. "Is my proposal/refactor/shiny
| thing going to make a customer want to spend more money, sign
| additional/longer contracts, or somehow smooth out support &
| operations?"
|
| If I get an "ehhh im not sure" or a more honest "no", then I'd be
| inclined to shitcan the proposal unless a very clear argument can
| be put forth for why this refactor will somehow _eventually_
| result in downstream value-add.
|
| Arguments along the axis of "because its the right thing to do",
| "this is the technical best practice" or "it's cool to look at"
| are rejected by default unless additional supporting evidence can
| be brought forth. Proposals that bring additional vendors &
| operational liabilities without also bringing _obvious_ value are
| to be considered malicious suggestions.
|
| This probably sounds draconian as hell to _some_ of HN, but if
| you are working with truly complex software, you cannot be
| playing games in traffic, _especially_ if you are already in
| production for many customers and under half-decade-long
| contracts.
|
| The time to be clever is on your side projects. Do not conflate
| the thing that results in your _paycheck_ with entertainment. It
| might seem like there could be some happy co-existence, but I 've
| personally never seen it work out.
| eternityforest wrote:
| I love best practices, and I'm even a fan of cargo cult
| development to a certain degree, staying with what's tested,
| reducing novelty, making things repeatable and declarative,
| choosing a million line framework over 1000 I'm house lines....
| But it's not worth trying to maintain your own Ansible in your
| own custom language with a GUI in your own GUI toolkit, just to
| avoid some ugly hacks and workarounds you'd need if you just
| used the popular tool.
| Aeolun wrote:
| I'm still more or less happy with the CRUDController I built more
| than 10 years ago.
| janmarsal wrote:
| Just when I think I've learned my lesson it happens again.
| code_runner wrote:
| things like this always have some underlying notion that there
| are objectively correct combinations of lines of code. There
| isn't. The cleverness problem can live at any level in a
| system.... this line is really clever because in <language> <data
| type> is really just a <other data type>, so if we use this
| <unrelated feature> we actually can do this all in one line!
|
| You can also have system level cleverness, which is fine as long
| as you can explain the cleverness in a few sentences. Sometimes
| the data access layer is clever and the other stuff isn't....
|
| at the end of the day, as long as you leave things such that they
| are easy to change, or at least you're brave enough to change
| them, none of it really matters. Someone is going to replace the
| "clever" code with other code that does more or less the same
| thing, and then next engineer will replace that code with
| something else.
| gnat wrote:
| Larry Wall said something like "if you have a complex problem,
| the complexity will come up somewhere -- either you have a
| complex language and a simple program, or a simple language and a
| complex problem. The complexity can't be wished away." Being
| Larry, he said it far better but I can't find the exact quote.
|
| All programming is making models: representing a chaotic and
| changing analog world in bits, data structures, and algorithms.
| For every model there's something that breaks that model.
| Attempting to incorporate every exception into your model leads
| you to the path of tuples + a workflow engine, and at that point
| all you've done is build a different database and programming
| language and move the complexity further up the stack.
|
| As requirements change, the model you built will no longer prove
| appropriate _because now you 're modeling different situations_.
| There's an art to building a model that doesn't box you in to the
| point where the first change request doesn't break your model.
| But at some point, every model is going to be inadequate.
|
| A senior programmer brings to this: knowing the common ways
| things change, the costs of changing different types of model,
| and the understanding that there's no such thing as Perfect so
| don't proclaim your framework to be such.
| thom wrote:
| Very well put. Effective businesses are good at communicating
| and sharing these models so everyone is able to reason about
| them, and if they're lucky anticipate future articulation
| points.
| kaliszad wrote:
| It seems, in some subcultures the FactoryFactory or
| ServiceService approach to reducing repetition and increasing
| complexity is common. In Lisps/ Scheme the best approach is
| usually the copy&paste until you established it is a problem and
| then introduce some wrapper function or a macro or whatever.
| retrocryptid wrote:
| I would argue you can be clever in one place if it lowers the
| overall complexity of the code.
|
| In the Lisp community, we try to be "clever by composition" and
| not "clever by subclassing" and... it makes code where you have
| to have more than one thing in mind at a time, but if you can do
| that it's often MUCH simpler than subclasses.
|
| A practical example might be a collections "class" (actually just
| a few functions) in old-school lucid lisp to do the same thing
| Java/Smalltalk collections did. Instead of subclassing for Set,
| Dictionary, OrderedCollection, etc. We had an "insert" function
| that could call functions to see if we could insert a particular
| element and where to insert it if we could. If you wanted Set
| semantics, you returned false from the first function if the
| element was in the collection. If you wanted OrderedCollection
| semantics, you did a binary search to find where to insert the
| element.
|
| It worked well if you were the type of programmer who couldn't
| remember the Collections class hierarchy semantics but could
| remember how to write two functions. I'm sure we had a few helper
| functions for common cases.
|
| It's not a panacea, but every now and again you can lower the
| overall complexity by raising the complexity of a small portion
| of code. It is, as the author has discovered, hard to figure out
| how to do it right the first time through.
| strken wrote:
| The CRUDController example is interesting because the problem is
| not exactly cleverness, but more a design without good escape
| hatches. It's bad because the pathway from a CRUDController to a
| controller that does more is a total rewrite.
| deathanatos wrote:
| Ha, I ran into one of these once. I didn't write it.
|
| Even our database records of "OAuth clients" went through it. I
| had wanted to make an optimization to those: I wanted to inline
| the OAuth client IDs for the mobile & web applications into the
| source code1. The beast that was the CRUD controller stopped me
| in my tracks. I eventually gave up -- too much complexity all
| muddled into one thing.
|
| It was also "cleverly" general in that our admin interface built
| off it, and the UI thus built the table showing you what records
| existed ... generically! Similarly, you could generically CRUD
| records. And one day ... someone deleted the mobile client's
| record from the OAuth client's table, which of course breaks the
| mobile app and caused an outage, and lead me to wonder "why do we
| even have this lever, Kronk?" (While maybe it made sense to
| delete some random script's OAuth client ... not so much the
| mobile app1.)
|
| Treating everything as a generic object that can be CRUD'd is one
| of those extremely leaky abstractions, and as the article gets
| at, it's usually the business logic that will reveal where the
| holes in the hull are.
|
| We had another class of "objects" which were user uploads, and
| they went through about 4 different storage models (they started
| as rows in the database, and that didn't scale well at all --
| they ended up as S3 objects which was okay-ish). But my
| predecessors, when they added a newer, better way of storing
| these ... didn't migrate the old ones. So the read logic had to
| be ready for a v1 upload or a v4 upload. A minor nightmare. So
| add "the DB model changes and is never migrated" to the list of
| things that screw these things up.
|
| (I _still_ see devs doing this -- adding newer, better means of
| storing something but not migrating the old records -- and it 's
| massive tech debt, every time I've seen it. Inevitably, I've had
| someone want to do a "give me all the data" style query, and that
| necessitates dealing with all the caked on layers of upgrades.
|
| My current company has this in the form of audit records. They
| started in the database ... which ... didn't ... scale ... and
| now they're in ... a bucket in a cloud's blob storage, which is
| okay-ish.
|
| So if you ever find yourself in one of these ... please, please
| migrate the old data.)
|
| I don't think codegen is the answer. I don't think codegen is
| ever the answer ... "what happens when the generator changes?" is
| something I never see addressed. The old code rots, typically.
|
| (1because a.) unless the company folded, they weren't going to
| change, so I figured that a commit to remove the literal mobile
| app was fine in that case and b.) that meant that, for those
| clients, we didn't need to make DB requests to that table. IIRC
| we did this on every query.)
| sam0x17 wrote:
| This sort of scheme is fine as long is there a way of overriding
| any particular piece of logic on the implementer's side.
| sebastianconcpt wrote:
| This is a junior issue fixable with proper social alignment with
| the team or business process. If the team has no adequate know-
| how transfer process then there is no right way to properly
| abstract and understand any business model.
|
| Saying "Don't be clever" is like saying, let me give you a piece
| of advice: "Don't be mistaken". I've personally find that level
| of vagueness very annoying.
| WesolyKubeczek wrote:
| The problem OP describes (multiple classes like that other class,
| but with a twist each) is a textbook use case for inheritance,
| but after decades of prominent programmers indoctrinating
| ,,inheritance bad", I can see why he would not consider it at
| all.
| TheIronMark wrote:
| The takeaway seems less about nifty coding and more about working
| in isolation. The author found a path he thought would solve the
| problem and was 'clever', but never really got feedback from
| peers. I've done that myself; been so enamored with my own idea
| that I failed to get feedback and ultimately delivered a non-
| optimal solution.
| mock-possum wrote:
| Aw, but I like being clever :(
___________________________________________________________________
(page generated 2023-08-03 23:01 UTC)