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