19 ms·
Don't be clever
- turnsout 3y agoIt seems like every coder needs to learn this the hard way at some point. “I’ll save so much time!”
- foobarian 3y agoI 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 3y agoI 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 3y agoYeah, 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 3y agoNow 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 3y agoHonestly 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 3y agoPeople 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.
- valenterry 3y agoJajajaja, we know - "clever" doesn't mean "smart" anymore but "overcomplicated". Nothing to see here, please move on...
- otikik 3y agoYour code shouldn’t be “smart”, most of the time anyway. Definitely not for a CRUD web app.
- WesolyKubeczek 3y agoWhich 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.
- janmarsal 3y agoJust when I think I've learned my lesson it happens again.
- report-to-trees 3y agoI 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.
- Verdex 3y agoIt 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_ 3y agoI 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 3y agoI 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 3y agoCleverness in the right place is good and useful. Cleverness in the wrong place is stupidity wearing nicer clothes.
- ygouzerh 3y ago[dead]
- BossingAround 3y agoTo 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.
- chasd00 3y ago> 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.
- otikik 3y ago> 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
- bloopernova 3y agoYeah, 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 3y agoTbh if your nodejs devs can’t handle some Python then you need better devs. I’d been happy to help!
- bloopernova 3y agoSome 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 3y ago> 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 3y agoFound 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 3y agoAt 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.
- catchnear4321 3y agothat’s the danger. you built a system for cogs. someone hired a thinker.
- BowBun 3y agoFirst 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 3y agoIf 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/c9ef2d955e3f99cfdf2fb99e4ef3591d.jpg https://forum.donanimhaber.com/store/c9/ef/2d/c9ef2d955e3f99...
- SanderNL 3y agoI 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.
- jstimpfle 3y agoI'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 3y agoIf 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.
- nperez 3y agoI 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.
- jbandela1 3y ago> 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.
- jader201 3y agoMy 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 3y agoYes, 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.
- xyzzy4747 3y agoThis is also something the Rust language does well with macros. It’s a pretty standard approach.
- fuzzy2 3y agoThis 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.
- dmichulke 3y ago> ... 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
- DaanDL 3y agoAh yes, the CrudController to solve all your problems. Also in this list: the AbstractBaseRepository
- kaliszad 3y agoIt 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.
- jasmer 3y ago[dead]
- ravenstine 3y agoI 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.
- assbuttbuttass 3y agoAgreed, when someone calls my code "complex" or "clever," it comes across as an insult
- ravenstine 3y agoI 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 3y agoI agree with you and am reminded of "KISS" (keep it simple, stupid): https://en.m.wikipedia.org/wiki/KISS_principle https://en.m.wikipedia.org/wiki/KISS_principle
- theshrike79 3y agoIt'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.
- layer8 3y agoThe 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 3y agoI 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.
- deleted 3y ago[deleted]
- Jun8 3y agoOn 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 https://news.ycombinator.com/item?id=22348386
- WesolyKubeczek 3y agoThe 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.
- WesolyKubeczek 3y agoThanks for all the downvotes, they prove my point brilliantly.
- joshstrange 3y agoI 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.
- BoxFour 3y agoThe 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 3y agoExperience will tell you that this happens often, and it’s often better not to DRY in the first place.
- BoxFour 3y agoIf 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.
- orblivion 3y agoWhat about making all of the "clever" things composable and optional, instead of tightly coupled? That way when you get a special case you can just make that part a special handler. You neither need to throw out the whole "clever" thing nor make the "clever" thing "more clever".
- BoxFour 3y agoChoosing to inherit from a base class always seems like an optional and intentional choice, no?
- karaterobot 3y agoThis 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 3y agoFifteen 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?
- Cutman 3y agoThe 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.
- strken 3y agoThe 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 3y agoHa, 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 code¹. 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 app¹.) 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. (¹because 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.)
- bqm 3y agoSomeone 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 3y agoAs 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...
- imdsm 3y ago"Brent" -- I had a chuckle at this unintentional coincidence
- charles_f 3y agoThis 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 https://en.m.wikipedia.org/wiki/Composition_over_inheritance
- sam0x17 3y agoThis sort of scheme is fine as long is there a way of overriding any particular piece of logic on the implementer's side.
- bronxpockfabz 3y agoI've being there (tm)
- at_a_remove 3y agoI 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.
- dclowd9901 3y ago> 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 3y agoApparently 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 3y agoOr don't use inheritance or automated copying. Use composition.
- contravariant 3y agoThis 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.
- theshrike79 3y agoIf 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.
- TheIronMark 3y agoThe 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.
- retrocryptid 3y agoI 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.
- sebastianconcpt 3y agoThis 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.
- mcv 3y agoI 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.
- duderific 3y agoI guess he wanted to make sure that the subclass code would be as simple as possible. So when he wanted to add a new entity to his model, as he mentioned, he only had to extend the abstract class, without writing any new business logic. Clearly what you're saying is correct, however, it would mess up his original intent, which was frictionless extensibility.
- xwowsersx 3y agoNot 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 3y agoI 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 3y agoFor 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.
- Sujeto 3y agoI 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.
- burglins 3y agoOh 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).
- ChrisMarshallNY 3y agoAs 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.
- FreshStart 3y ago... otherwise you become unmanageable and subvert the reality assumptions of the ruling caste.
- motoboi 3y agoCode 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.
- GTP 3y agoI never heard about class generators before, could someone please point me to some resources to learn more about it?
- brundolf 3y agoI 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
- neontomo 3y agoWhile 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
- vbo 3y agoSo 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.
- erikpukinskis 3y agoI’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.
- firefoxd 3y agoRight 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 3y agoIf 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.
- code_runner 3y agothings 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.
- slifin 3y agoComposer/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
- bellajbadr 3y agoNo body cared about the startup left with the junk code -_-
- pachico 3y agoI 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.
- log101 3y agoI did exactly the same thing lately, calendar had to be delayed 2 weeks at the end, almost got fired .)
- lolsal 3y agoThe 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.
- bob1029 3y agoMy 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 3y agoI 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.
- eternityforest 3y agoEveryone 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.
- mock-possum 3y agoAw, but I like being clever :(
- gnat 3y agoLarry 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 3y agoVery 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.
- partdavid 3y agoSo many times I've had people regurgitate this blog post[1], or KISS, or some other general piece of advice to "keep things simple" and it just means they want to influence the shell game of managing complexity so the complexity is someone else's problem--and they can stop thinking about it. I've come to really hate when someone pulls this out as some general principle, because it so frequently means they want to push complexity from an implementation (which maybe has a hope of hiding some of the complexity) out into callers or configurations or some other kind of leaf, where it becomes a breeding ground for manually synchronized configurations, copy-and-paste code promotion (not reuse, which is bad enough, but the pattern where some thing that you're forcing people to manually compile or explode has to be copy-and-modified through an artifact promotion. Frankly, KISS and its ilk have become more often "things uninsightful people say to make them sound clever and vaguely iconoclastic" than something that can be actually useful as guidance: because people think they remove nuance rather than adding it (KISS is great if you honor its nuances like "and no simpler..." and "considered as a whole"; but instead it's a thought-terminating cliché). I've heard people use these sound bites to justify, e.g., the entire notion of automated testing (too complicated), or "infrastructure as code"--though I also dislike this term and favor "software-defined infrastructure", though maybe if it became popular, objections that software-defined infrastructure isn't really software would become as common as objections that infrastructure-as-code isn't really code. ----- [1] I don't mean this literal blog post, I mean the same kinds of "don't be too clever" platitudes.
- 0x4164 3y agoYeah, 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.
- Aeolun 3y agoI’m still more or less happy with the CRUDController I built more than 10 years ago.
- _samjarman 3y ago"Clever code isn't"
- uwagar 3y agoyeah so dumb programmers that follow you can understand your code.
- agentultra 3y agoIndirection is not an abstraction. An abstraction creates a new language, a new semantic layer, that has a precise definition. You can express laws and properties from its definition. This lets you think in the new semantic layer without thinking about the lower levels of complexity. But yes… the pattern class problem is real. a good number of us have all done it once or twice.
- mdip 3y agoMaybe. OK, I grasp what the author is getting at -- there are certainly times when I've gone needlessly complex expecting a set of problems that not only never materialized, but the ones that actually materialized were poorly handled by my design. It sucks. Lesson learned. I only have first-person anec-data based on a diverse career in software development (big/small/solo/small/big team/process-heavy/haphazard) but it's the opposite circumstance that usually burns me far worse. I describe this as "they asked for this really small thing and now it runs half the company" problem. It's an exaggeration, but the story from my past was the temporary system that was set up for an (unnamed) major long-distance carrier to "pay the bill via an EDI setup"[1]. Ten years later, shortly after we moved a huge data-center to another time zone the panic alarm bells rang over a few million dollars missing from said carrier. The temporary-turned-permanent setup was an IBM NetVista machine hooked up to a 1200 baud modem with a dedicated phone line running a DOS program at boot off of a hard drive that had to have its disk parameters manually provided via BIOS every reboot because the little battery inside had sent its last electron down the line and -- rather than replace the battery with an abundance of spares -- the technician decided printing a label with the values would suffice. A more apt example would be a dashboard that I created a simple service for which produced a list of "the top 20 endpoint threats by vulnerability patching level" ... oh did the execs love this[2], which led to creating a mess of other dashboards and a set of small services that produced just the hostnames. They were all variations like "Just locations with desktop support staff on-site by location", "Servers", "Domain Controllers" ... I want to say I had something like 30 variations at the end ... and thousands of lines of code. I wasn't clever. This "stupid service" took me away from things I needed to finish and several others that I enjoyed working on much more, so this was a model of spaghetti-code-copy-pasta(-marinara). And then they wanted something added to the output of everything. I finished it a few days before leaving the department ... along the way I figured out that more than half of the reports produced results that were anywhere from "kind of close" to what they said they did to several which produced results for the wrong building for each endpoint type (and -- as a result -- was completely ignored). I bit the bullet and took this 5,000 multi-file ASP (vbscript!) monster down to 6-ish classes, less than 500 lines, easy to extend/expand and re-use what's there[3]. Unfortunately, this leads to the totally useless advice of "Be Clever When It Will Help." When will it help? Beats the hell out of me. I didn't imagine a world where a VP calling my desk asking for "a quick web page that shows the worst machines on the network for a slide I'm working on" would become an integral reporting component of an in-house massive networking auditing tool, so I can't help much, there... [0] Admittedly, I only discovered I encountered the opposite circumstance after that circumstance presented itself, so the whole thing benefited from hindsight bias. [1] I was not directly involved in the software involved, only in the resurrection of the dead service. [2] I had the dubious distinction of accidentally creating "a new operations process" whereby every week it was expected that the top 20 machines on Monday (...which became "from each list"...) would be fully resolved by Friday. Desktop had to have dolls in my shape with knives sticking out of them. [3] Demonstrated on the final revision by adding a single class that altered the retrieval process to WeakReference cache all views (there were reasons an output cache wasn't useful due to it now being part of a bigger system that often required previously materialized values on other threads but never touched an HTTP endpoint).
- whartung 3y agoMy most productive time working on DB applications was when I worked with a code generator. You laid out the entry screens, specified the tables and columns, fired off the generator and magic happens. You could slam out a basic maintenance CRUD screen, with search, foreign key popups in an hour! But here’s the meat of if. If you ever needed to go back and update that program, you could do it in minutes. Add some validation logic? Mechanically trivial. Say a column was added to the table and you just wanted to populate but not even show it on the screen. 5 minutes. If you wanted to add it to the screen the hard part was deciding where to put it, making room for it, putting it in the right focus order. But coding it? Once it was part of the screen the rest came for free. See the trick was that unlike pretty much every other code generator I’ve seen (which I think are better termed wizards), this code generator was not a one shot deal. Your code was in a separate file and the generator merged your changes with the generated code to create the final result. So for simple changes, your code file was quite small as the generator built up all the boiler plate around it. A very (very) crude example is imagine using a typical one shot generator, making changes to the result, capturing those as a diff patch file, and in the future you did all your work in the patch file, applying it any time you needed to rerun the code generator. That won’t work in practice, but when the patch files uses more semantic indicators, it does work. It worked really well. This makes the code generator part and parcel to the workflow, and you get to leverage it again and again. In many of these systems, and especially modern frameworks, they have a happy path that can do 80+% of the common task, but more often than not you hit that 81% mark and you have to toss the baby out with the bath water. Not only is the tooling not able to take you where you want to go, it can’t even take you partway and you’re on your own and your stuck with 100% of the work instead of the extra 20%. I never really encountered that using this toolset. I had few examples where you should not rerun the generator any more, but it was really rare.
- dajonker 3y agoAt a previous job we used ActiveAdmin on top of Rails, which is a sort of CRUDController as described in the article. Wonderful productivity when you are just starting out, but it got so bloated that it took over a minute to hot reload code. You basically had to kill the server and restart it when changing anything. Nobody dared doing anything about it and nobody had any idea how to fix the issues. But there were scripts to make it easier to kill and restart the development server... So at some point I wrote a script that read the ActiveAdmin parts and used a library to convert it into its abstract syntax tree. Now I could manipulate the tree and shape it into regular Rails controllers and plain old HTML views. That took care of about 90% of the work. The remaining 10% was of course the weird exceptions. Finishing it took a lot of testing, a ballsy deploy to production, and a further hour of frantic bug fixing, but then it was done. No more productivity destroying hassling with the smart admin framework. I think I would have quit my job way earlier if I wasn't able to fix that, and had to live with it instead.
- Khelavaster 3y agoThis dude isn't aware of MVC with scaffolding from entities....
- 10g1k 3y agoKISS.