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