[HN Gopher] The mindless tyranny of "what if it changes?" as a s...
       ___________________________________________________________________
        
       The mindless tyranny of "what if it changes?" as a software design
       principle
        
       Author : goostavos
       Score  : 91 points
       Date   : 2022-05-22 21:31 UTC (1 hours ago)
        
 (HTM) web link (chriskiehl.com)
 (TXT) w3m dump (chriskiehl.com)
        
       | weatherlight wrote:
       | What if it needs to change is better than, "What if it changes?"
       | 
       | In a business environment, Write code that is meant to be
       | extended upon by people other than you.
       | 
       | It's not mindless tyranny, its good design.
        
         | throwawayboise wrote:
         | That's fine, as long as you are still solving the original
         | problem in the required time. The IRS won't excuse that you
         | missed filing your withholding data on time because you were
         | making the reporting tool easier to extend upon later.
        
         | lumost wrote:
         | Rewriting code is often faster than understanding obtuse code
         | written for requirements that never come.
         | 
         | I once encountered a 5k loc program designed to take the
         | average of a series of Boolean values. The code had been
         | designed such that the input data format could be changed etc
         | etc. unfortunately, of the 5 metrics which could have ever been
         | requested only 2 were implemented. It was to difficult to work
         | with the code to implement the three others ( just finding
         | where to implement them took 3 days).
         | 
         | Ultimately all the extra abstraction abstracted over the wrong
         | things. The 5kloc project was replaced with a 100 line file
         | that did exactly what was needed.
        
       | not2b wrote:
       | The idea should be that it probably will change, and to prepare
       | for that you need to code it a way that if it does change, you
       | have to update the code only in one or two places, you haven't
       | scattered the knowledge of that detail all over the code.
        
         | est31 wrote:
         | If you overdo that, you end up with more abstraction than is
         | helpful, which will hurt your ability to refactor as well. When
         | I was younger, I've done a lot of abstractions because doing
         | abstractions was cool. Now as I'm older and wiser, I only add
         | interfaces if it's really needed. Even if I know that there
         | will be some abstraction coming in the future, I felt it more
         | helpful to add the abstraction only right before implementing
         | the second use case for it, because then I have thought a lot
         | about how the second use case is to be implemented.
        
           | hamburglar wrote:
           | I think an important skill you pick up after a couple decades
           | is to know the difference between "this is likely to change"
           | and "my gut says what-if-it-changes but my experience tells
           | me YAGNI"
           | 
           | My general approach is to write my code in a way that could
           | easily be turned into an interface once a second or third
           | implementation needs to be introduced and hope a junior
           | doesn't come along and fuck it up in the meantime. I think
           | one reason people go to the abstract interface early (when
           | there is only one impl) is that they see it as a guard
           | against someone else coming along and changing the well
           | thought out layering. But it doesn't work and just makes
           | things harder to read and work with.
        
       | civilized wrote:
       | Reminds me of FizzBuzzEnterpriseEdition .
       | https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpris...
       | 
       | You never know when you might need to change the implementation
       | of how the "Fuzz" string is returned, so you need a
       | FuzzStringReturner.
       | 
       | And you never know when you might need multiple different ways of
       | returning "Fuzz", so you need a FuzzStringReturnerFactory.
       | 
       | And for SOLID it's important to separate concerns, so you want
       | your FuzzStringReturnerFactory separate from your
       | FuzzStringPrinterFactory.
       | 
       | And that barely scratches the surface of what you need!
        
         | Spivak wrote:
         | FizzBuzzEnterprise is just "closed to modification" / "open to
         | extension" taken to the extreme on a trivial problem.
        
           | doctor_eval wrote:
           | Except that sometimes it's hard to tell when the problem
           | you're working on is trivial. I saw this pattern used as a
           | source of data for a drop down list of office locations. It
           | should have been "select id, name from office_locations". It
           | really was that simple. But it was an "enterprise app", so
           | instead we had 5 classes and so much more.
        
       | draw_down wrote:
       | Hmm. I think the principle of not making trap-door decisions is
       | still important though.
       | 
       | You shouldn't spend a bunch of time preparing for something that
       | never comes, but you should consider which scenarios would leave
       | you totally screwed.
        
         | jameshart wrote:
         | Yes, this.
         | 
         | The reason I'm asking you, in this code review, "what if it
         | changes?" is not because I'm infected with enterprisitis and
         | must overcomplicate every system. It's because I fear the
         | answer to "what if it changes" is "this software, and
         | everything that gets built to rely upon it over the next year,
         | will need to be rewritten from scratch".
         | 
         | And I would rather that we not take that bet without thinking
         | through the odds.
        
       | lhnz wrote:
       | I think "what if it changes?" can also be used to argue for more
       | concrete, simpler code that is less DRY and therefore can be
       | rewritten or deleted with greater ease.
       | 
       | I wouldn't refer to overly generic code as easier to change.
        
       | ChrisMarshallNY wrote:
       | Heh. It's something to keep in mind, but can be dangerous to
       | design an architecture for.
       | 
       | There's a few things that need to be designed in from the
       | beginning, like security, localization, and error handling, but a
       | lot of other stuff can be hardcoded or deferred.
       | 
       | In my experience, every project is different, and "hard and fast"
       | rules are to be avoided, if possible.
       | 
       | Reminds me of the classic joke _The Toaster_ (and it is
       | "classic." That specification in the second-from-last paragraph
       | was supposed to be over-the-top excessive):
       | Once upon a time, in a kingdom not far from here, a king summoned
       | two of his advisors for a test.  He showed them both a shiny
       | metal box with two slots in the top, a control knob, and a lever.
       | "What do you think this is?"              One advisor, an
       | engineer, answered first.  "It  is a toaster,"   he said.  The
       | king asked, "How would you design an embedded computer for it?"
       | The engineer replied, "Using a four-bit microcontroller, I would
       | write a simple program that reads the darkness knob and quantizes
       | its position to one of 16 shades of darkness, from snow white to
       | coal black.  The program would use that darkness level as the
       | index to a 16-element table of initial timer values.  Then it
       | would turn on the heating elements and start the timer with the
       | initial value selected from the table.  At the end of the time
       | delay, it would turn off the heat and pop up the toast.  Come
       | back next week, and I'll show you a working prototype."
       | The second advisor, a computer scientist, immediately recognized
       | the danger of such short-sighted thinking.  He said, "Toasters
       | don't just turn bread into toast, they are also used to warm
       | frozen waffles.  What you see before you is really a breakfast
       | food cooker.  As the subjects of your kingdom become more
       | sophisticated, they will demand more capabilities.  They will
       | need a breakfast food cooker that can also cook sausage, fry
       | bacon, and make scrambled eggs.  A toaster that only makes toast
       | will soon be obsolete.  If we don't look to the future, we will
       | have to completely redesign the toaster in just a few years."
       | "With this in mind, we can formulate a more intelligent solution
       | to the problem.  First, create a class of breakfast foods.
       | Specialize this class into subclasses:  grains, pork, and
       | poultry.  The specialization process should be repeated with
       | grains divided into toast, muffins, pancakes, and waffles;  pork
       | divided into sausage, links, and bacon;  and poultry divided into
       | scrambled eggs, hard-boiled eggs, poached eggs, fried eggs, and
       | various omelet classes."              "The ham and cheese omelet
       | class is worth special attention because it must inherit
       | characteristics from the pork, dairy, and poultry classes.  Thus,
       | we see that the problem cannot be properly solved without
       | multiple inheritance. At run time, the program must create the
       | proper object and send a message to the object that says, 'Cook
       | yourself.' The semantics of this message depend, of course, on
       | the kind of object, so they have a different meaning to a piece
       | of toast than to scrambled eggs."              "Reviewing the
       | process so far, we see that the analysis phase has revealed that
       | the primary requirement is to cook any kind of breakfast food.
       | In the design phase, we have discovered some derived
       | requirements.  Specifically, we need an object-oriented language
       | with multiple inheritance.  Of course, users don't want the eggs
       | to get cold while the bacon is frying, so concurrent processing
       | is required, too."              "We must not forget the user
       | interface.  The lever that lowers the food lacks versatility, and
       | the darkness knob is confusing.  Users won't buy the product
       | unless it has a user-friendly, graphical interface.  When the
       | breakfast cooker is plugged in, users should see a cowboy boot on
       | the screen.  Users click on it, and the message 'Booting UNIX v.
       | 8.3' appears on the screen.  (UNIX 8.3 should be out by the time
       | the product gets to the market.) Users can pull down a menu and
       | click on the foods they want to cook."              "Having made
       | the wise decision of specifying the software first in the design
       | phase, all that remains is to pick an adequate hardware platform
       | for the implementation phase.  An Intel 80386 with 8MB of memory,
       | a 30MB hard disk, and a VGA monitor should be sufficient.  If you
       | select a multitasking, object oriented language that supports
       | multiple inheritance and has a built-in GUI, writing the program
       | will be a snap.  (Imagine the difficulty we would have had if we
       | had foolishly allowed a hardware-first design strategy to lock us
       | into a four-bit microcontroller!)."              The king had the
       | computer scientist thrown in the moat, and they all lived happily
       | ever after.
        
         | contravariant wrote:
         | Of course the simplest toasters don't have microcontrollers and
         | simply have a heat sensitive piece of metal which turns off the
         | electromagnet that keeps the toast down.
        
       | WalterBright wrote:
       | My personal bugaboo is worrying about if the size of `int`
       | changes. It's not going to change. It's 32 bits. 25 years ago was
       | the end of 16 bits. 25 years ago.
       | 
       | Even if you do want to target real mode DOS, good luck getting
       | your modern app to fit in 64K. Heck, just upper-casing a Unicode
       | string takes 640K.
        
       | Philip-J-Fry wrote:
       | "What if it changes?" is a reasonable question to ask. But every
       | time you do you are walking a tightrope. My rule of thumb is that
       | we look at what is in use TODAY, and then write a decent
       | abstraction around that. If something is used once, ignore any
       | abstractions. If it's used twice, just copy it, it's better. If
       | it's used 3 or more times, look at writing an abstraction that
       | suits us TODAY not for the future. Bonus points if the
       | abstraction allows us to extend easily in the future, but nothing
       | should be justified with a "what if".
       | 
       | The reason a lot of Java or C# code is written with all these
       | abstractions is because it aids unit testing. But I've come to
       | love just doing integration testing. I still use unit testing to
       | test complex logic, but things like "does this struct mapper work
       | correctly" are ignored, we'll find out from our integration
       | tests. If our integration tests work, we've fulfilled our part of
       | the contract, that's all we care about. Focus on writing them and
       | making them fast and easy to run. It's virtually no different to
       | unit tests but just 10x easier to maintain.
        
         | doctor_eval wrote:
         | I could not agree more with this.
         | 
         | I would add, though, that in my experience you can often
         | identity parts of a design that are more likely to change than
         | others (for example, due to "known unknowns").
         | 
         | I've used microservices to solve this problem in the past.
         | Write a service that does what you know today, and rewrite it
         | tomorrow when you know more. The first step helps you identify
         | the interfaces, the second step lets you improve the logic.
         | 
         | In my experience this approach gives you a good trade off
         | between minimal abstraction and maximum flexibility.
         | 
         | (Of course lots of people pooh-pooh microservices as adding a
         | bunch of complexity, but that hasn't been my experience at all
         | - quite the opposite in fact)
        
         | ThrustVectoring wrote:
         | Java in particular is missing certain language features
         | necessary for easily changing code functionality. This leads to
         | abstractions getting written in to the code so that they _can_
         | be added if needed later.
         | 
         | A specific example is getters and setters for class variables.
         | If another class directly accesses a variable, you have to
         | change _both_ classes to replace direct access with methods
         | that do additional work. In other languages (Python
         | specifically), you can change the callee so that direct access
         | gets delegated to specific functions, and the caller doesn 't
         | have to care about that refactor.
        
       | jackblemming wrote:
       | Good code rarely needs to change because it's complete. It's
       | meant to be built on top of, rather than modified for every new
       | consumer. Think standard libraries. There is no reason for the
       | linked list module to ever change unless it's for bug fixes or
       | performance improvements.
       | 
       | Business logic needs to change all the time, because businesses
       | are always changing. This is why we separate it out cleanly, so
       | it can change easily.
       | 
       | Know what type of code you're writing so you can plan and design
       | appropriately.
        
         | taeric wrote:
         | Meh. Good code is a weasel term. Code can be easy to extend.
         | Easy to change. Easy to throw away. Sometimes tradeoffs are
         | made between those options.
         | 
         | This is like saying a good piece of furniture is easy to add
         | to. I mean, maybe?
        
         | gutnor wrote:
         | Corollary of that is that MVP for library code is very
         | different than MVP for business code.
         | 
         | MVP for business code is a great way to get the tool in front
         | of the users and get traction, request for more work. Once you
         | release your library, desire for changes basically drops to 0.
         | 
         | It's working. If it's clunky, the clunkiness just gets wrapped
         | into a utility class somewhere deep in the belly your client
         | application with about 1 commit change per year to change the
         | copyright notice.
         | 
         | Similarly your corporate leverage falls to 0. You make a
         | library to save people time, congrats you did it. Every update
         | you ask people to do that does not bring new feature they need
         | reduce your value. Good luck justifying a cosmetic change ROI.
        
         | lr4444lr wrote:
         | Hard disagree. I wish the world worked that way, but it
         | doesn't. So much happens in the time between the product
         | inception and delivery. Initial proposal of value, prototypes,
         | customer feedback and requests, known performance issues to
         | work around, making hooks for non-devs to execute important
         | overrides... the list goes on, and contracts have dates that
         | have to be met. Stuff can't be rewritten to address every turn.
         | This is why Agile (however abused it is) largely beat
         | Waterfall. Software should serve people and yield to their
         | needs. Not the other way around.
        
         | hyperpape wrote:
         | Pointing to a list makes the problem look too easy, because
         | it's such a clearly defined abstraction. Of course a linked
         | list mostly doesn't need to change--it's an easy problem.
         | 
         | Give me a definition of good code that applies to:
         | 
         | - the standard library
         | 
         | - a gui toolkit
         | 
         | - the kernel
         | 
         | - the apps built on top of vendor provided gui toolkits that
         | change
         | 
         | - a web application backend
         | 
         | - a web application front-end
         | 
         | - a database
         | 
         | - more things I'm forgetting
        
         | hnxs wrote:
         | good code is code that gets you promoted, increases your
         | networth, and helps you retire faster
         | 
         | depending on your work environment, good code may actually be
         | bad code.
        
         | agentultra wrote:
         | Was going to swoop in to say this!
         | 
         | I would add that sometimes leaving oneself _room_ to expand
         | /respond to changes is just what your code needs. Expansion
         | points. Whatever you want to call it.
         | 
         | The maxim I use is, "avoid being overly specific." If you have
         | polymorphism in your language the less you say about the type
         | of a variable the more places it can be used and the more ways
         | it can be composed with other functions. This requires a style
         | of design that pushes _side effects_ to the edges of the
         | program (consequently where they 're the easiest to change).
         | 
         | With this style of programming responding to change is
         | straight-forward to reason about. No need for complicated
         | indirection between objects and tracing behaviors through
         | v-tables. If you are using OOP keep your data and behaviors
         | separate.
         | 
         | The stuff that solidifies rarely changes. The stuff built on it
         | changes a lot. And as time goes on you'll find that refactors
         | will start pushing more upper layers down where they will
         | eventually solidify.
        
       | kache_ wrote:
       | All software is incorrect given a long enough time frame.
       | Whatever, just get paid and make the problem go away. Be short
       | sighted, it works
        
       | onion2k wrote:
       | I've worked with people who would prefer a complex function to
       | generate a list of properties from a data source over a much
       | simpler hardcoded list, on the basis that if a new option is
       | added its easier. They used this pattern for things like asset
       | classes, which admittedly did change about once every 5 years. It
       | made me sad.
        
       | scrozier wrote:
       | Judging from the comments, it's possible that satire doesn't
       | translate easily around the world.
        
         | TameAntelope wrote:
         | Seems to me that everyone understands the satire, and is
         | discussing why the satire needed to be written; because people
         | actually behave this way.
        
         | brunooliv wrote:
         | Took me too many comments to reach the obvious one. I guess
         | some people take things too seriously :')
        
         | [deleted]
        
         | lolc wrote:
         | That's just people "speedreading" and then commenting post-
         | haste. Because they have a few interfaces to implement still.
        
         | [deleted]
        
       | strictfp wrote:
       | I think the most important mind shift is from "let's make this
       | extendable by plugins/scriptable so we can modify it while it's
       | live" to "if requirements change, let's just change the source
       | code and redeploy".
       | 
       | I also disagree with the SOLID principles. KISS is more important
       | than adding extra code and sacrificing performance to allow
       | extension without touching the original source files. Unless you
       | goal is explicitly that.
       | 
       | You're trying to write the simplest, most straight-forward
       | encoding of the solution. If you can avoid duplication and make
       | the code read well, you're golden.
        
         | [deleted]
        
         | Spivak wrote:
         | > if requirements change, let's just change the source code and
         | redeploy
         | 
         | The intractable problem ends up being "fuck, half our code base
         | implicitly depended on the current behavior and now we can't
         | change the it without half our tests failing."
         | 
         | This is why 10,000 ft abstractions can sometimes be nice
         | because now all your business logic exists in a fantasy world
         | of interfaces you control.
        
       | thanatos519 wrote:
       | I'll take the problem of occasionally propagating a disruptive
       | change through a large codebase over daily wading through a dozen
       | 2-line functions in multiple repos just to make any progress.
        
       | skybrian wrote:
       | This is an old debate. The counter-slogan is "You Aren't Going To
       | Need It." [1] All you can say from the outside is "well, it
       | depends what it is."
       | 
       | Often the best way to design for change is to make it easy to
       | edit the code, test it, commit, and deploy, but not everything is
       | a web app.
       | 
       | [1] https://martinfowler.com/bliki/Yagni.html
        
         | aqme28 wrote:
         | I wouldn't say that YAGNI is the counter-slogan. Rather, it's
         | the overarching principle. It's not going to change. You aren't
         | going to need to code for that contingency.
        
       | brunooliv wrote:
       | It's satire, calm down y'all!
        
       | jitl wrote:
       | There's a lot of value to being able to command-click a function
       | or method call and jump to one single definition. This
       | substantially reduces friction when reading/understanding code or
       | change sets.
       | 
       | One of the best things about dynamic languages like Typescript is
       | that in these languages you can avoid interface/implementation
       | duality while still being able to mock or test code by using
       | test-only runtime mutation of class instances or modules.
        
         | taeric wrote:
         | This has been doable in static languages for a long time now,
         | as well.
        
           | abernard1 wrote:
           | You are correct, and for some reference, Mockito 1.0 came out
           | in 2014.
           | 
           | @Mock, @InjectMocks, and the ability to wire up an entire
           | dependency tree has been something Typescripters have still
           | not perfected like crusty Java devs.
        
         | piyh wrote:
         | Can you put that second sentence into simple english for those
         | imposters such as myself?
        
           | Jtsummers wrote:
           | They seem to be referring to "duck typing". The typing
           | principle exemplified by the statement: If it looks like a
           | duck, walks like a duck, and quacks like a duck, then it's a
           | duck. Does it matter that it's a waddling man in a duck
           | costume saying "quack"? Nope, still a duck. You don't need an
           | explicit interface and an explicit declaration that you're
           | implementing it, or to implement it fully, you just need to
           | implement the operations relevant to the use of the object.
           | 
           | You have a procedure or something that you want to test and
           | it takes, as a parameter, a logging service? You don't want
           | to instantiate a full logging service and set up the
           | database, because that's heavyweight for a test and
           | irrelevant for the particular test? Fine, you throw together
           | a quick and dirty logger that answers the method `log` and
           | pass that in instead. No need to know what the precise
           | interface has to be, or implement or stub out all its other
           | capabilities. You know it has a `log` method because the
           | procedure under test uses it, so that's what you give your
           | quick and dirty mock logger. No more, no less.
        
       | trhway wrote:
       | like everything, it'd definitely change. Adapting simple code for
       | a change is much simpler than adapting a complex code even when
       | that complex code supposedly had that change already baked-in
       | from the beginning, yet naturally almost always not exactly
       | right. 30+ years ago starting programming i was of an opposite
       | opinion :)
        
       | knowsuchagency wrote:
       | This hits too close to home
        
       | Benjammer wrote:
       | Reminds me of this classic:
       | https://programmingisterrible.com/post/139222674273/how-to-w...
        
       | IncRnd wrote:
       | This article is very humorous. It is also a saddening reflection
       | on software development as it is often practiced.
        
       ___________________________________________________________________
       (page generated 2022-05-22 23:00 UTC)