Write code that is easy to delete, not easy to extend (2016)

(programmingisterrible.com)

92 points | by stanulilic 3 hours ago ago

35 comments

  • jumploops 2 hours ago

    My favorite saying: “simple is robust”

    Similar in spirit to Lehman’s Law of Continuing Change[0], the idea is that the less complexity a system has, the easier it is to change.

    Rather than plan for the future with extensible code, plan for the future with straightforward code.

    E.g. only abstract when the situation requires it, encourage simple duplication, use monoliths up front, scale vertically before horizontally, etc.

    I’ve built many 0-1 systems, and this is the common thread among all of them.

    [0] https://en.m.wikipedia.org/wiki/Lehman%27s_laws_of_software_...

    • zokier 2 hours ago

      Sure, but when applying "simple is robust" principle it is extremely important to understand also intrinsic complexity. Not handling edge-cases etc does not make for robust code, no matter how much simpler it is.

      • z3t4 2 hours ago

        There will always be edge cases, and yes they will make the code more complicated, but what really helps is automatic testing to make sure those edge cases don't break when making changes.

      • immibis 12 minutes ago

        Failing to account for this gives you Wayland (which at this time is more complex than X11)

    • antonhag an hour ago

      +1, but I'm not sure if the "simple is robust" saying is straightforward enough? It opens up to discussion about what "simple" means and how it applies to the system (which apparently is a complex enough question to warrant the attention of the brilliant Rich Hickey).

      Maybe "dumb is robust" or "straightforward is robust" capture the sentiment better?

      • jumploops an hour ago

        The usual metric is complexity, but that can be hard to measure in every instance.

        Used within a team setting, what is simple is entirely subjective to that set of experiences.

        Example: Redis is dead simple, but it's also an additional service. Depending on the team, the problem, and the scale, it might be best to use your existing RDBMS. A different set of circumstances may make Redis the best choice.

        Note: I love "dumb is robust," as it ties simplicity and straightforwardness together, but I'm concerned it may carry an unnecessarily negative connotation to both the problems and the team.

        Simple isn't necessarily dumb.

    • arkh 2 hours ago

      > encourage simple duplication

      A rule I like to follow:

      - first time: write it

      - second time: copy it

      - third time: maybe refactor it

      • jumploops an hour ago

        Agreed! I'll usually go one step further for early projects and lean towards 3rd time copy, 4th time refactor.

        Example: So much early code is boilerplate CRUD, that it's tempting to abstract it. 9 times out of 10, you'll create a quasi-ORM that starts inheriting business logic and quickly grows omni-functions.

        Eventually you may actually need this layer, assuming you're system miraculously scales to needing multiple services, datastores, and regions.

        However this doesn't just apply to the obvious, and you may find omni-logic that made a feature more simple once and is currently blocking N new features.

        Code is cheap, especially today. Complexity necessarily constrains, for better or worse.

        • tetha an hour ago

          Hence why I am rather looking if two pieces of code change together, opposed to just looking the same.

          If I need to introduce the same feature in multiple places in roughly the same way, that's a decent indication code wants to be the same and wants to change together. That's something to consider extracting.

          Fixing the same bug in several places is a similar, but weaker indication. It's weaker, because a bug might also occur from using a framework or a library wrong and you do that in several places. Fixing the same business logic error in several places could mean to centralize some things.

  • dang 2 hours ago

    Related:

    Write code that is easy to delete, not easy to extend (2016) - https://news.ycombinator.com/item?id=24989351 - Nov 2020 (30 comments)

    Write code that is easy to delete, not easy to extend (2016) - https://news.ycombinator.com/item?id=23914486 - July 2020 (109 comments)

    Write code that is easy to delete, not easy to extend - https://news.ycombinator.com/item?id=18761739 - Dec 2018 (2 comments)

    Write code that is easy to delete, not easy to extend - https://news.ycombinator.com/item?id=11093733 - Feb 2016 (133 comments)

  • Terr_ 3 hours ago

    Yep, to recycle a brief analysis of my own youthful mistakes:

    ____

    I've come to believe the opposite, promoting it as "Design for Deletion."

    I used to think I could make a wonderful work of art which everyone will appreciate for the ages, crafted so that every contingency is planned for, every need met... But nobody predicts future needs that well. Someday whatever I make is going to be That Stupid Thing to somebody, and they're going to be justified demolishing the whole mess, no matter how proud I may feel about it now.

    So instead, put effort into making it easy to remove. This often ends up reducing coupling, but--crucially--it's not the same as some enthusiastic young developer trying to decouple all the things through a meta-configurable framework. Sometimes a tight coupling is better when it's easier to reason about. [...]

    https://news.ycombinator.com/item?id=41219130

    • KronisLV 2 hours ago

      > So instead, put effort into making it easy to remove.

      You might, but there's also going to be other people that will happily go ahead and create abstractions and logic that will form the very core of a project and entrench themselves to such a degree that they're impossible to get rid of.

      For example, you might stumble upon CommonExcelFileParser, CommonExcelFileParserUtilities, HasExcelParseStatus, ProductImportExcelParser, ProductImportExcelParserView, ProductImportExcelParserResultHandler and who knows what else, the kind of stuff that ends up being foundational for the code around it, much like how if you start a front end project in React or Angular, migrating to anything else would be a Sisyphean task.

      In practice, that means that people end up building a whole platform and you basically have to stick with it, even though some of the choices made might cause bunches of problems in the future and, due to all of the coupling, refactoring is way harder than it would be in an under-abstracted codebase.

      I'm not sure what to do then. People seem to like doing that more than applying KISS and YAGNI and making code easy to delete.

      • ffsm8 2 hours ago

        Not my originals, and I cannot recall who said this... But it's completely on point

        * Software has a tendency to become maximally complex. You either have an actually complex domain, or the developers will find a way to increase the complexity (..because otherwise, they're bored)

        * Good software is modular and easy to remove. Consequently, good software will keep getting replaced until it's bad and cannot be removed anymore

        • the_gipsy an hour ago

          Hard to remove doesn't mean impossible to remove.

          Refactoring or fixing bad codebases is a thing.

    • ozim 3 hours ago

      It still depends. Business line application yes and 10x yes. It will change it will move, don’t try to foresee business requirements. Just write something that will be easy to replace or throw away.

      Frameworks and libraries not really, for those you still have to adjust to whatever happens in the world but at much saner pace.

      Biggest issue is when devs want to write “framework” when they work on business line application where they have frameworks that they are already using like Rails/Asp.Net etc.

      • rob74 2 hours ago

        I would say the biggest issue are the frameworks themselves: they practically force you to fit your code to their architecture, and before you know it, your logic is split across innumerable classes. Laravel (with which I have the most experience) has models, controllers, views, service providers, data transfer objects etc. etc. - that makes it (arguably) easier to write and extend code, but very hard to refactor/delete.

      • planb 2 hours ago

        > Business line application yes and 10x yes. It will change it will move, don’t try to foresee business requirements. Just write something that will be easy to replace or throw away.

        This is correct, but from my experience of working in the same company for over a decade: You'll learn to foresee requirements. Especially the "we'll never need that" ones that become business critical after a few months/years...

        • Terr_ an hour ago

          Like the path that starts with a "simple" system of "soft deletes" for Foo records, which progresses through a period of developer-assisted "restores" or merges, and then they want even older info, and to make reports...

          However it would have all been so much easier if they'd realized their business domain called for "Foo Revisions" in the first place.

    • Affric 3 hours ago

      Sometimes things change, sometimes we chose the wrong abstraction.

      Unless you’re writing the Linux kernel you shouldn’t write it like the Linux kernel.

  • nextcaller 3 hours ago

    Implementing choice is superior. Not only can your program be capable of more actions, but the process of thinking about how to include these features leads to focusing on your codebase which leads to refactoring, better code. With time the code becomes so flexible that adding features is easy, because your foundation is superior. And in the process other core functionality gets fixed and becomes better.

    • alserio 2 hours ago

      Can you explain what you mean with "implementing choice"?

      • nextcaller 2 hours ago

        This was written in the context of a discussion about showing resistance or not to feature requests by users sorry for the confusion.

  • ollysb 2 hours ago

    Once you can load up a full codebase into an LLM I'm hoping the cost to update client code is significantly reduced. Then you could focus on evolving the design without all the grunt work.

    • qwertox 2 hours ago

      I'm also betting on this, that one day I'll be able to dump a codebase into an LLM and it will clean up the code. Not rewrite it, not restructure it, just clean it up. Remove unused code and comment it sensibly. Maybe also suggest some tests for it and implement them separately.

      • Cthulhu_ an hour ago

        Copilot already does this, at least for individual chunks of code (and text, for that matter). Not for a whole codebase, but I think that's going to be a matter of time.

  • Dwedit 3 hours ago

    C# is pretty good about these, with extension methods and event handlers. With event handlers instead of virtual methods, it's much easier to separate the pieces.

    • sam_lowry_ 3 hours ago

      And yet the worst ever code I saw was in C#.

      I hope I won't offend anyone pointing at it [1]. This is a somewhat popular tool to evaluate macroeconomic policies in the EU.

      A combination of language choice (C# as a natural language Microsoft Excel bosses tend to request from their interns to use), the usual churn of academic undergrads and loads of other cultural failures are the reasons this monster exsts.

      Someone should write a book how to make the worst ever codebase, and start with EUROMOD.

      [1] https://github.com/ec-jrc/JRC-EUROMOD-software-source-code

      • willtemperley an hour ago

        I could write about creating the worst possible environment to be a software developer, having worked at the JRC for five years.

        I'm not sure how constructive that would be. I'm still hurting because the IT department decided the only way to deploy my Java app was through rsyncing to a running Tomcat installation, allowing class files from several deployments previous to resurface in memory causing some beautiful bugs.

        Or the time they decided to buy a Hadoop cluster at a cost of EUR 100k which I told IT dept they wouldn't be able to connect to from the outside world because the network rules are carved in stone. They bought it, and guess what, network ops said no.

        The ten foot high touch screen and the car emissions data stored in Excel files and the 80 million euros spent on a website or the time the partner research group refused to release the data we had funded so we couldn't run workshops or release the project (around EUR 2 million).

        The waste.

        • sam_lowry_ an hour ago

          > rsyncing to a running Tomcat installation

          You can delete while resync'ing but I guess the issue is not in resyncing itself, but rather in the disempowerment of individual contributors.

          You could have argued to add --delete for your case, as well as requesting a shutdown before and a start after, but I guess explaining this to countless morons is too much to ask from a humble developer.

          OTOH, this resyncing story probably means that you were allowed to choose the wrong development framework to start with. Because resyncing PHP is much more reasonable.

          • willtemperley an hour ago

            No the issue was files cached in memory. No amount of deleting from the file system is going to delete files cached by the servlet, which is why the servlet itself needs to be restarted.

  • ainiriand 2 hours ago

    Why not both?

  • worstspotgain an hour ago

    At the risk of turning a unison into a chord, here's my two cents.

    If:

    1. You know where the 'creases' of orthogonality are. You've carved the turkey 1000 times and you never get it wrong anymore.

    2. As a result, there is hardly any difference in complexity between code that is and isn't easy to extend.

    Then write code that is easy to extend, not delete.

    The question is whether your impression of the above is true. It won't be for most junior developers, and for many senior ones. If orthogonality isn't something you preoccupy yourself with, it probably won't be.

    In my experience, the most telling heuristic is rewriting propensity. I'm talking about rewriting while writing, not about refactoring later. Unless something is obvious, you won't get the right design on the first write. You certainly won't get the correct extensible design. If you're instructed to write it just once, then by all means make it easy to delete.

    • blitzar an hour ago

      > The question is whether your impression of the above is true

      If you think you are good enough to qualify you almost certainly don't qualify. If you do qualify then chances are you probably don't think you do.

  • revskill 2 hours ago

    Write test, not code.

    • ttyprintk an hour ago

      Specifically, write tests that identify disposable code. More specifically, you hopefully wrote some disposable code that is a modular extension of something close to the core. Write tests that demonstrate which of those deserves to be core, and which is necessary for a requirement but disposable. Since the article brings up shared apis, hopefully when you arrive on a new project, those are well understood as requirements paired with test cases. Repeat in the opposite direction in dependent projects.