17 ms·
“What if it changes?”
- scrozier 4y agoJudging from the comments, it's possible that satire doesn't translate easily around the world.
- lolc 4y agoThat's just people "speedreading" and then commenting post-haste. Because they have a few interfaces to implement still.
- brunooliv 4y agoTook me too many comments to reach the obvious one. I guess some people take things too seriously :')
- deleted 4y ago[deleted]
- deleted 4y ago[deleted]
- TameAntelope 4y agoSeems to me that everyone understands the satire, and is discussing why the satire needed to be written; because people actually behave this way.
- magpi3 4y agoI think game developers are at the front lines of changing requirements and that is why they build scriptable engines and not programs perse. I think engines and libraries are the ultimate solution if you want to program defensively.
- Aeolun 4y agoHmm, I understand the point of the article, but 4/5 times when I expect something to change, it actually does. And people that implement only the specific version end up having to implement the general version anyway.
- weatherlight 4y agoWhat 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 4y agoThat'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 4y agoRewriting 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.
- jahewson 4y agoUnless the person other than you is me. I’ll just delete your monstrosity and replace it with a couple hundred lines of focused, simple code and revel in the mostly red diff on GitHub.
- not2b 4y agoThe 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 4y agoIf 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 4y agoI 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.
- draw_down 4y agoHmm. 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 4y agoYes, 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 4y agoI 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 4y agoHeh. 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.
- jackblemming 4y agoGood 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.
- agentultra 4y agoWas 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.
- taeric 4y agoMeh. 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?
- nine_k 4y agoTo my mind, one of the important aspects is that code must be easy to understand, so as to write it correctly.
- 4y ago
- kache_ 4y agoAll 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 4y agoI'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.
- scintill76 4y agoStill peeved about times I was forced to do this, when any meaningful change would also necessitate a code change anyway.
- jitl 4y agoThere’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 4y agoThis has been doable in static languages for a long time now, as well.
- b3morales 4y agoDepends what you mean by "static". Swapping out a method or class implementation at runtime is pretty inherently a dynamic task.
- taeric 4y agoApologies, I assumed "static typed" was a fairly safe assumption.
- abernard1 4y agoYou 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 4y agoCan you put that second sentence into simple english for those imposters such as myself?
- Jtsummers 4y agoThey 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.
- strictfp 4y agoI 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 4y ago[deleted]
- Spivak 4y ago> 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.
- mmis1000 4y agoI think "don't make everything plugin-able" doesn't mean "Let's hard glue everything". And if change a single file can break half of unit tests. I think your test is bad at best. (Or there is only integration tests because you already hot glued them?)
- doctor_eval 4y agoAgree, this is why I like microservices, because they enable exactly this, and make it easy. Sort out your abstractions at a high level (auth, storage, routing, messaging, etc) and then write small programs to implement the logic. If one of them is wrong - redeploy.
- theteapot 4y ago> "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"." People who develop web apps can go ahead and have this epiphany, but this obviously doesn't work where you don't have complete control over "redeploy", which is a large class of software.
- thanatos519 4y agoI'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.
- trhway 4y agolike 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 :)
- skybrian 4y agoThis 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 https://martinfowler.com/bliki/Yagni.html
- aqme28 4y agoI 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.
- ncmncm 4y agoThat was the joke. The author is delightfully subtle. I worked at a big company that assigned its summer interns to write an online sarcasm detector. At end of summer, they announced they had been completely successful. No one asked for evidence. Clearly the students had understood the assignment perfectly. Anyway, it was said that this had been assigned. I never spoke to anyone actually involved. Or, admitted it.
- brunooliv 4y agoIt's satire, calm down y'all!
- knowsuchagency 4y agoThis hits too close to home
- Benjammer 4y agoReminds me of this classic: https://programmingisterrible.com/post/139222674273/how-to-write-disposable-code-in-large-systems https://programmingisterrible.com/post/139222674273/how-to-w...
- ChrisMarshallNY 4y agoThat's great! Thanks!
- nocman 4y agoNot sure how I missed seeing this before today, but thank you very much for bringing it up. That is a particularly good article. It has a lot of food for thought.
- swah 4y agoI love tef's writing - and now he takes pictures of crows full time! :)
- IncRnd 4y agoThis article is very humorous. It is also a saddening reflection on software development as it is often practiced.
- ncmncm 4y agoWhat is sad is all the people who took it dead seriously, and agreed.
- civilized 4y agoReminds me of FizzBuzzEnterpriseEdition . https://github.com/EnterpriseQualityCoding/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 4y agoFizzBuzzEnterprise is just "closed to modification" / "open to extension" taken to the extreme on a trivial problem.
- doctor_eval 4y agoExcept 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.
- civilized 4y agoIt would be easier if people were encouraged to find the simplest solutions in their education, rather than being indoctrinated with somebody's long list of Patterns that will somehow make your code Good if you do them enough.
- mepiethree 4y agoMaybe we just work at completely different kinds of places, but I’ve worked with a ton of people who don’t know what patterns are and very few who over-apply them
- 4y ago
- WalterBright 4y agoMy 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.
- oriolid 4y agoIt's 32 bits only if you ignore microcontrollers and DSPs. Also, the size of 'long int' is definitely not constant if your code needs to be portable between Windows and something else. I have written some DSP code that was portable between architectures that did not have same sized _char_. Thankfully it was almost 20 years ago and TMS320C55x that had 16-bit char and 16-bit int is AFAIK dead.
- WalterBright 4y ago> It's 32 bits only if you ignore microcontrollers and DSPs. I know. But you're not likely to port any apps between microcontrollers, DSPs, and general purpose machines. > Also, the size of 'long int' is definitely not constant if your code needs to be portable between Windows and something else. It's also not portable between Linux and Mac OSX. This makes `long int` a completely useless type. Use `int` and `long long`. > that did not have same sized _char_ I remember one person who responded to me about a DSP that had sizeof(char)==32, and how it was wonderful that the C Standard accommodated that so code could be portable. I challenged him to port the C implementation of `diff` to it and see how far he gets with it. I predicted total failure :-) The C and C++ standards would do the programming world a favor by standardizing on: 1. 2's complement 2. fixed sizes for char, short, int and long 3. dumping any character sets other than Unicode Machines that don't support that will require a customized compiler anyway, and it's very unlikely any code will be portable to it without significant rewrite.
- oriolid 4y ago> I remember one person who responded to me about a DSP that had sizeof(char)==32, and how it was wonderful that the C Standard accommodated that so code could be portable. The support for 32-bit char doesn't automatically make code portable between architectures with different char sizes. But it's what makes it possible to write a C compiler at all for those weird architectures. It certainly doesn't make code that assumes things portable to architectures where those assumptions do not hold. > The C and C++ standards would do the programming world a favor by standardizing on: 1. 2's complement 2. fixed sizes for char, short, int and long 3. dumping any character sets other than Unicode This would be a great favour to embedded developers everywhere, because it would finally free us from the C legacy. Rust and Zig look promising, and at some point I even thought D might work but now I understand why it wouldn't. I wonder if you're aware of the standard sized type aliases int8_t, int16_t etc and what's your opinion of them.
- Philip-J-Fry 4y ago"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 4y agoI 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)
- Mr_P 4y agoMicroservices is just OOP/dependency-injection, but with RPCs instead of function calls. The same criticisms for microservices (claims that it adds complexity, or too many pieces) are also seen for OOP. Curiously, while folks sometimes complain about breaking up a system into smaller microservices or smaller classes, nobody every complains about being asked to break up an essay into paragraphs.
- GuB-42 4y ago> Developers from certain languages [Java]... I am mostly a C++ developer but I have been on some Java projects recently, and I am a bit shocked by the "what if it changes?" culture. Lots of abstraction, weak coupling, design patterns, etc... It looks like a cult to the gang of four. Of course, it is not exclusive to Java, and these patterns are here for a reason, but I have a feeling that Java developers tend to overdo it.
- humanrebar 4y agoOn the other hand, Java projects are better tested and more portable in my experience.
- nullcipher 4y agomore portable.. to what ? are we talking of desktop applications here? I can't remember the last java program I used on the desktop.
- iratewizard 4y agoYou can get around a lot of this by writing or buying infrastructure. What if it changes is most applicable to business logic, and your business logic should be something you can throw away and rebuild without a care in the world.
- cush 4y agoThrow SOLID out the window. Hard-coded everything. Then we can bake our business logic into FPGAs and get all the performance!
- gilmore606 4y agoOnce humans stop writing code and let AGIs do it, that is what code will look like. And it will be glorious. 90% of the code we write is pathetic shims and scaffolds to let our extremely-limited minds work on it, not useful instructions for a computer to execute our will.
- dleslie 4y agoFar, far too alluring is the draw to solve broader and deeper problems under the presumption that there will be a future requirement to match changes in customer demand. I take a hardline mantra that the best code is code that ships, ships quickly, and satisfies customer need. Only if, and less likely, when customer requirements shift in a way to demand an aggressive broad or deep change then is it appropriate to do so.
- civilized 4y ago> Never let anyone explore the answer to your "what if it changes?" question. The impact of such a change is irrelevant! For the question to retain its power, fear must live in the imagination. The change's impact must be unstated, horrible, and so fraught with Lovecraftian terrors that only your approach can defend against it. I see this in a lot of contexts at work, not just defensive coding. Red tape that slows us down a lot, supposedly protecting us against unimaginably horrible things which no one can seem to articulate under questioning. I mean, look how red that tape is! It must be protecting us against something pretty dangerous. Red means danger, right?
- gitgud 4y agoWrite code for replacement not reuse If you write the system in a way where parts are decoupled and understandable, they can easily be replaced/modified when circumstances change... Because they always do...
- thatswrong0 4y agoThis is probably a bit of an aside to the implied problem at hand but.. considering this from the React side of my company's codebase, I wish this question was at least kept softly in mind when designing components. I frequently encounter components that were very clearly created as "one-shot"s that are then unfortunately extended by piling on more props and conditional behavior by the next developers who need something like the current component but ever so slightly different. Often the solution initially would have been to separate out the presentation side of the component from the behavioral wrapper that chooses what data needs to be shown / what actions are performed by interactions. By the time the component arrives at my lap (because I too need something same same but different), however, it has become a monstrosity that can take a long time to disentangle via ADD (anger driven development). I think asking oneself a simple question such as "how would someone make this search box work with a different data source?" would probably result in components that are decomposed into simpler, smaller parts that allow for much easier reuse and adaptation. On the flip side, I'm also of the belief that the second developer to touch a component is necessarily better equipped to answer that question, so the onus should probably be on them to make the proper generalizing changes. (I'm still trying to figure out how to write a document that expresses this idea more concretely to my coworkers because it often isn't quite this simple..) ¯\_(ツ)_/¯
- bcrosby95 4y ago> On the flip side, I'm also of the belief that the second developer to touch a component is necessarily better equipped to answer that question, so the onus should probably be on them to make the proper generalizing changes. This. Given a long enough timeline, pretty much all abstractions fail. People are too timid to replace them when they do, and given enough churn, the code gets out of hand.
- aprdm 4y agoSecond developer that touched it might be new to the company, or in a deadline, you never know
- thatswrong0 4y agoRight - the former case is one that’s unavoidable and, in the case the first developer didn’t get it quite right, should ideally be covered by code review, but doing a good code review is IMO pretty hard, and giving that sort of feedback requires that the reviewer also understands the problems at hand (which in our case is frequently not the case as our development teams are small and we’ll often have a backend focused dev reviewing another backend focused dev’s frontend work). And in the latter case, at least in my organization, we don’t often have hard “must meet” deadlines, but instead have more self-imposed deadlines that become externalized and concrete when these “deadlines” (aka ship date estimates) are communicated org wide. So all I can figure to do is give zoom talks, write documentation that’s shared to all developers, and try to encourage coworkers to take the opportunity to push soft deadlines back if needed to pay down tech debt.
- AtlasBarfed 4y agoThis is understandably voiced... and trying to anticipate change isn't bad, but... devil is in the details, and to a certain degree this keeps things from getting too concrete to the point you can't solve the problem. I think "prototype to determine it solves the immediate problem, but keep an eye on change as you do it" is far better advice, it keeps the horse in front of the cart.
- RajT88 4y agoThis reminds me of the policy of some of my CS professors: If it's longer than 2 lines, it belongs in a function. (Really guys?) I can appreciate a few layers of indirection as being useful for maintenance later on - handling new corner cases by giving you some opportunities for making the most minimal possible change. There's a diminishing return after a few layers, and then of course you start to make maintenance itself harder by making it harder to understand and navigate.
- marmshallow 4y agoIs this article satire? It does not offer any bounds to imagination nor scope creep. Seems like a good way to justify writing code that doesn’t help achieve immediate (or even foreseeable) goals.
- ncmncm 4y agoWell spotted. Gold star!
- lucas_membrane 4y agoIgnoring the possibility of a specific change or kind of change is not so bad if there is only 1 line of code or 1 block of code that ignores it. Beyond that, you are wandering you know not where.
- gkop 4y agoSay you’re piloting a sailboat downwind and there’s an obstacle between you and your destination. You will need to plot a course to circumvent the obstacle. You have a choice to turn the boat toward the wind (edit: not through the the wind, just toward it) to pass the obstacle on one side, or away from the wind to pass on the other side. One of these two options retains optionality at little or no cost, that’s likely to be the better choice. Likewise in software, look for zero cost opportunities to retain optionality (eg. free ways to defer decisions till later). This is a bit sophisticated to teach junior developers, so we just teach them “consider the implications of your design decisions with regard to supporting future needs, including those not yet known.” Yes, you can certainly over-index on this dimension, but that doesn’t make it a useless or necessarily harmful consideration. (Not implying the article disagrees with this; it does appear to be satire)
- Aeolun 4y ago> One of these two options retains optionality at little or no cost I may misunderstand your analogy here, but it seems to me that retaining optionality has a significant up-front cost here that may never be realized?
- gkop 4y agoYou turn toward the wind, then you’ll be more upwind until later. So you retain optionality by having “more room to go downwind”. (Please if we have a more experienced sailor here, do explain it better!) What up-front cost are you thinking of? A non-sailing version might be say you’re on a bicycle, and you can drop down the mountain now and go lateral later, or go lateral now and drop down later, and either way you arrive at your destination after the same time and effort. Going lateral now retains optionality. Very open to hearing better analogies for the same concept! Perhaps from the world of business?
- Aeolun 4y agoI interpreted ‘turning towards the wind’ as actually trying to sail against the direction of the wind (very hard/annoying). So trying to do that first for gains you may never realize is not a great idea. Like trying to drive your bike uphill so that may easily go downhill at some later point in time. The biking example makes it clearer what you meant (to me anyhow).
- Linda703 4y ago[dead]
- gerdesj 4y ago"Shy away from numbers or concrete analysis during design. Good engineers estimate. Great engineers speculate." "Shy away from numbers": I'm writing a calculator! "concrete analysis": The intersection of Civ Eng and Maths? "Good engineers": Hire estimators. An Estimator is often an Engineer (Chartered) and they estimate. The Estimator is generally rather better than the Engineer in estimating, because it is their specialisation. Of course, I jest a bit but the essay was a bit slack in my estimation. We all have to deal with change on a far more granular basis than daily or even hourly. The world is a fluid environment rather than a static place and we have to live with that and we do quite routinely. What I think our putative expert on change is describing is an approach for dealing with unexpected change. However, I think we deal with unexpected changes all the time and we do it quite well - our senses and abilities show that: Think about how complicated driving a car is.
- stephc_int13 4y agoGood software engineering practice is sometimes (or often) the opposite of the academic CS dogma. Asking questions about specific things, like numbers and hardware is in fact relevant. Also, future-proofing is one of the worst and most difficult to avoid mistake you can make as a novice or intermediate dev.
- deleted 4y ago[deleted]
- c3534l 4y agoI don't like this authors approach. Always ask the question, but never add complexity because you assume you know the answer. If you have "multiple layers of indirection" that is "unused" because of paranoia that you might have to refactor your code, then you're doing it wrong. Write code such that you never have to be afraid to refactor it. That's not write code that never has to be refactored. In fact, refactor often and liberally. And if things break when you refactor, then that code is bad code. But don't add worthless layers of indirection because you write code that can't be tested, full of side effects, requiring entire components be rewritten from scratch, etc. The top commenter says good code rarely needs to be changed. I think thats foolish. Good code is constantly changed, because its good enough it can be.
- mjevans 4y agoTry to produce useful interfaces and data structures, even internally. When data is stored make it easy to verify that it's in a recognized, expected, format; possibly with a version scheme that can be extended later. Historically successful protocols often have a basic feature level and later extend it with features that are not required (at the time) but improve functionality. All the more so when users with accounts need more features than federated (not authenticated) users from other platforms.
- deleted 4y ago[deleted]
- ncmncm 4y agoLook up Poe's Law.
- deleted 4y ago[deleted]
- wellpast 4y agoThe problem I’ve always encountered is that layers of abstraction are often prematurely introduced and cost immensely because the programmer believes certain things won’t change. Most abstractions are premature and the world always changes against initial assumptions. So the answer to “what if it changes?” is it most certainly will and in a way inconsistent with your cute abstraction.
- raydiatian 4y agoI hope this article is satire. Knowing that abstraction is going to add value vs. speculating that it will? That should be the difference between adding the extra investment upfront vs. inciting YAGNI (you aint gonna need it). The footprint of “times you thought you didn’t need it but actually did” is extremely small. Also those are called “learning opportunities” not “mistakes”. This article is ridiculous.
- kromem 4y agoOf course it is satire.
- PeterisP 4y agoFor every "what if it changes?" question you can (and should!) get someone with a decent long-term experience (for this question, skill isn't sufficient, years/decades matter) in that business domain (preferably also software maintenance in that business domain) to say how likely it is to actually change. You can ask that question about anything, but the answer should be different depending on the actual likelihood of change.
- eternalban 4y agoData schemas and APIs need to address extensibility. For the latter this does not mean that it requires a "cathedral" but simply effective abstraction. For the former higher normal forms are helpful. Code can be viewed as disposable as long as the above two concerns are addressed.
- Subsentient 4y agoMy personal rule of thumb is that if someone is making gurglings about X possibly happening in the future, I will probably take some steps to make it easier if X does indeed happen in the future. If it's something I think is pretty much universal, I don't bother. "What if X" is not good enough, you also need "they were talking about/speculating about X". This rule tends to serve me well.
- sergiotapia 4y agoThe term is architecture astronauts. Some may have good intentions, most are just stroking it to their code. Keep them in check!
- ncmncm 4y agoPoe's Law strikes again! YAGNI indeed. For those who didn't get it... This article is a sly dig on fresh-graduated architecture astronauts. The tone is absolutely pitch-perfect, giving nothing away to anyone who is not entirely sure it is a jape, until the last line. Priceless.
- ehnto 4y agoMy only criticism of this piece, is that it's so dry and well articulated, some might not realize it's satire. There is some conversation here about the number of instances something has to happen before you should abstract it, which is a handy rule of thumb. You should also consider the tradeoff of complexity, sometimes even if you have 5, 10, 15 snippets of code that are almost the same you still don't need to abstract it, because the differences are not complex to manage, but an abstraction would be.
- chunkyks 4y agoI haven't seen anyone else point out: One of the ways you can often identify really experienced programmers is by them being able to pretty accurately separate thing-that-probably-will-change from things-that-probably-won't. Finding a good way to balance YAGNI with this-will-definitely-change-in-a-few-months-because-it-always-does is incredibly hard, and I've really appreciated working with engineers who make that prediction correctly.
- creakingstairs 4y agoI think there are also very low hanging fruits that doesn't take more effort but makes something way more future proof. Experienced engineers can identify those, and most of the low hanging fruits seem to be DB design related.
- hinkley 4y agoI'd say it goes even beyond that into being able to separate the hypothetical future problems that will be a minor irritation from the ones that will wreck your whole month. As you get to learn more failure modes for software, you start to realize you can't plug all the holes in the dyke, not if you had absolute control of every hand on your team. You can stop four problems or you can hedge against twenty. The problem is that hedging is way harder to explain to people. It's how we ended up with unsatisfying concepts like 'code smells'. It's not evil, it might not even be that bad, but... don't do it anyway.
- deleted 4y ago[deleted]
- JeremyNT 4y ago> Finding a good way to balance YAGNI with this-will-definitely-change-in-a-few-months-because-it-always-does is incredibly hard, and I've really appreciated working with engineers who make that prediction correctly. Is this a generalizable skill? To me, it feels like changes are more often driven by shifting business requirements and ephemeral victories of internal political battles rather than sound technical reasons.
- angarg12 4y agoSince OP works in Amazon he might be familiar with the mental model of "one and two way doors". A one way door is a decision that is impossible or very difficult to undo or change. A two way door is easy to change. The idea is to spend most of your energy on one way door decisions and little in two way doors. This act as a remedy to things like bike shedding. If something is easy to change, just go ahead and do it! The relevance here is that we can apply this concept in reverse. If we make something easy to change, is close to a two way door. Hence we reduce the time we need to spend on it's design/consideration/ etc. Personally I like to write code that is more on the flexibler side to increase my optionality. I can then iterate faster throwing things at the wall and change my mind as needed. Of course this flexibility doesn't come for free. Overengineering and cost of carrying are real, so apply your best judgement.
- yen223 4y ago"One and two-way doors" is a nice way to phrase it. I use a similar heuristic to figure out where to spend more effort designing things upfront. For most web apps, the one-way door is usually going to be the database schema - data migrations are trickier to do once you have to deal with real data. The other big class of "one-way" decisions are with regards to code that live in environments you don't control, e.g. mobile and desktop apps. One tip I would offer is when building something new, you should try and delay making one-way decisions as long as you can, until you have a clearer picture of how things should work.
- dgb23 4y agoThis is really good advice. Flexibility is leverage. It’s not a “what if” question, but deliberately preparing for exploration and fast iteration.
- 1penny42cents 4y agoReminds me of this talk, "Stop Writing Classes": https://youtu.be/o9pEzgHorH0 https://youtu.be/o9pEzgHorH0 Changed my perspective on this newfound tool I'd learned to use called "indirection". My favorite quote is something like "don't waste 10 minutes now to save yourself 2 minutes later".
- DeathArrow 4y agoIn OOP world things can be hard to change if there is a over reliance on encapsulation and inheritance. For some people, making things easy yo change means making them more complicate, adding more layers of abstractions and patterns. I am using a more Data Oriented Approach [1] in which I keep the data separated from code, try to use immutable data structures and try to use methods and functions with as little side affects as possible. I am also trying to architect the software to be modular. I.e. split the code in classes grouped by functionalities, split it in different projects. And I have success at this even if I am using a language that's very object oriented and being part of a community who was used to put a very high price on object orientation, abstraction and design patterns. I'm referring to C#/.Net. [1] https://www.manning.com/books/data-oriented-programming https://www.manning.com/books/data-oriented-programming PS. This book and some articles and videos made by others on Data Oriented Programming can be huge helpers and big time savers for me. I'm no longer thinking in objects, I'm thinking in data and functions which process the data. I'm no longer a prisoner in the Kingdom of Nouns. [2] [2] http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom...
- commandlinefan 4y agoThis line of thinking is so unironically pervasive that I actually didn’t realize he was being satirical until I was halfway through the second pargraph.
- niels_bom 4y agoI always find it funny that short, not very interesting (imho) articles always get a tonne of discussion here on HN. Reminds me of Parkinson’s Law of Triviality, but that one’s for inside of an organization.
- deleted 4y ago[deleted]
- laputan_machine 4y ago> [1] If you need a good starting idea, consider pulling all of the methods out of one class and putting them into an interface of the same name. Then rename the original class impl. Do this for all classes with exactly one implementation for maximum effectiveness We have too many repos that have this behaviour. Every time I've raised it as pointless I get "You just don't understand the power of DI". I guess not.
- EdwardDiego 4y agoWhy are Java devs allegedly more prone to this than, say C# devs? Is it just snark? Or maybe the legacy of using a metric butt ton of reflective magic where interfaces with only one concrete implementation were common, cos Spring?
- throwaway2037 4y agoI have done both C# and Java "enterprise-y" programming. I don't really see the difference in the end product. If anything, Java was more open source oriented earlier, so the open source libraries look a bit different. It's a bit like a second version of New Zealand on the opposite side of the planet! They evolved slightly differently... But overall: I see so little difference for server side programming. (Please leave aside the Holy Wars about GUIs: C# XAML/WPF crushes, except for cross-platform where Java Swing was the only option for years.) But yes, I see your point about snark / mud slinging. (The best mud you can sling back at C#: "You didn't have solid UNIX support for 15 years".) I saw the same between Perl and Python people 20 years ago. Being "fluent" in both, I never understood the fuss. They felt so similar, except syntax. You could write imperative code that looked and worked the same... Finally, to me, the whole point of interface with "single" implementation is to easily facilitate mocking frameworks, like Java Mockito. (I am sure C# has something similar by 2022.) When the mock is created, byte code generates an ephemeral 2nd implementation. I never used Spring Framework much, but I think I can see what you mean when using @Autowire annotation constructors with interface type arguments. Spring can easily guess the correct implementation to inject.
- boltzmann_brain 4y ago
- imtringued 4y agoI once inherited a codebase where everything was hidden behind two interfaces for no reason whatsoever. A "Business Object interface" is implemented by a "Business Object implementation" which delegates its method calls to a "Data Access Object interface which defines exactly the same methods as the Business Object interface" which then is implemented by a "Data Access Object implementation" which then does a simple SQL query. You can argue that the DAO has a right to exist. After all, you are separating concerns and organizing your codebase. The BO only exists to make me suffer through 4 layers of indirection. When I write code I just drop the DAO interface and call the original class DAO. If I ever need to migrate to something else, sure, I could just rename that damn thing to DAOImpl and create an interface with the same name as the original class and the same methods. I never had to.
- Tade0 4y ago> If you need a good starting idea, consider pulling all of the methods out of one class and putting them into an interface of the same name. Then rename the original class impl. Do this for all classes with exactly one implementation for maximum effectiveness Or add "I" as a prefix to the interface's name. I always deflect this pattern of doubling code with: "what's the benefit of having it like that?" or "is this benefit worth the cost?". As for the main question: my reply is "I'll take responsibility for that and refactor this personally.". My experience is that good and bad code takes roughly the same time to write - the latter is just hard/impossible to modify once you delivered the feature originally requested. Code with unnecessary layers is of course hard to read and thus modify.
- Tomis02 4y ago- What if it changes? - What if it doesn't?
- galaxyLogic 4y agoInstead of preparing for possible future requirements we should strive to make the code easy to understand. THEN if the need arises to change it, that becomes easy. Not because the code was designed to handle possible future use-cases, but because it was designed to be easy to understand, and thus easy to modify without causing more bugs. If it turns out the possible future user-cases never materialize, we still benefit from the easy to understand code when we need to fix bugs in it. There are two different types of reuse: a) Supporting multiple use-cases of the same code, with different parameters. b) Modifying the code to do something else than it does currently. If the code is easy to understand it is easy to change for both cases. But if you try to make it ready for multiple use-cases from the start it becomes much harder to change it since it is already more complicated than it needs to be, to support just the initial user-case. There is no Silver Bullet. Code is our tool for dealing with complexity. But it does not remove complexity. And more code you create more complexity you will have.
- phtrivier 4y agoI wish the author had provided more examples of the kind of code they wish had not been written because "it might change in the future", because I'm not quite sure what they're advocating against. Wrapping external libraries ? It depends on the level of maturity of the library. I have seen HTTP libraries change their API often enough for a long time. I wrapped them because it did _change_. (Although I'd rather have fetch_my_foo wrapping calls to the lib that does http call, rather than having fetch_my_foo calling a wrapper that calls a lib) Wrapping input / output ? That can have _some_ uses in testing (Although it might be better to have unwrapped code doing the I/O, calling code that does the logic.) Hiding a class behind an interface that will only have a single Impl ? It the class is doing I/O, it might very well need at least a test implémentation. 'new FileInputStream(new Buffered stream(new Buffer(new Encoding( new ...' ? Yeah, this is typically the case where a proper API need to have 'File.read("xxx")' from the get go rather than force people to either wrap or share a wrapper. Etc,etc...
- po1nt 4y agoAnd here I am, raging over hardcoded stuff that should have been overrideable but now I'll need to fork or hunt down original developer of the library. Well things changed. Now what?
- t43562 4y agoI want to avoid closing the door on possibilities that I think likely in the future but a lot of that happens where you make commitments to other users such as in an API or in commandline options. I want there to be a way to add a "v2" to the api if I feel that I have to and I like the commandline option that says --build=android rather than --android because I feel I can more smoothly add allowed options that way. This is the area one needs to try to be forward thinking. One can way overdo even "door opening" and create pain and complexity for users. As for the code ..... I like to be able to read it. I prefer not to have to jump around every 5 seconds if possible. I like it to be easy to write tests for. If it's easy to test and I have lots of tests then changing it shouldn't be too stressful. I prefer to do things the simplest way until I have a good reason why it's not enough.
- JohnHaugeland 4y agoWhat if it changes is the right question to ask when completely unnecessarily tying yourself to a provider in active decline