Hacker News new | ask | show | jobs
by risaacs99 2584 days ago
The longer I've worked in software development, the less I'm convinced that any of this matters, at all.

What has mattered more to me is choosing the right architecture, technologies and dependencies. Understanding the fundamentals, especially at least a vague understanding of what the computer is doing with the code I am writing. Basic understanding of algorithms and techniques like state machines.

I've grown almost disdainful of things like SOLID principles, after taking them more seriously in the past.

16 comments

So glad to see this stated publicly, and actually getting some positive responses. 25+ years of software development and the best "pattern" I can identify after dozens of projects is just one: simplicity.

If you can't follow the logic by just reading the code, then maintenance will be slow and cumbersome. New development will eventually taper off as more and more time is spent fixing issues. I've written production systems, still in operation today decades later, that hold true to the simplicity principle. I wouldn't do it any other way. But, the battle I frequently face is with upstart devs who want to use some new pattern or principle because they read that it was "better" than straight-up code. And yes, they can implement some new feature quick, but 2 years down the road all hell breaks loose.

Some of my pet-peeve anti-patterns:

* "Plugin" systems only used by the primary development team. There's power in being able to follow the logic for any call stack by just reading the code; plugins make the flow much more challenging, and provide little value.

* Large (and complicated) run-time configuration IOC systems which make any debug session a dreadful process.

* Rules Engines. 'nuff said.

* Piles of abstractions, one atop the other, because teams had no time to find commonalities and/or sensible use-cases.

* 25+ new source code files to respond to a /heartbeat endpoint.

* Fixing a problem in 3 lines of code is not fixing anything if it complicates the debug process, forces a re-write of the unit test drivers, enables mysterious build failures or taxes the DevOps team.

And some of my favorite simplifying patterns:

* AOP for logging; but NOT (dear god) for business logic.

* 3 levels of abstraction, and no more. If you can't figure out a way to do it in 3 levels of abstraction, then something else is wrong.

* 3 systems of state: config (global), current request and persistence (database). If you need more than that, then something is wrong with the architecture. (Ok, compilers may not be able to get away with this, but most applications can, in my experience.)

* If you use a pattern (MVC, for example) apply it to the whole system. E.g. doing IOC only in the upstream API system when the rest of the code doesn't, just makes a mess.

SOLID is useful as a rule-of-thumb, but there's always one over-arching principle that should trump all others: YAGNI.

It's easy to build something simple (and perhaps naive) and make it more complex over time as your needs become selectively more complicated and your grasp of the domain more nuanced, but it's impossible to start complex and pare down to a more simple implementation. Even worse, your bad design decisions due to your less-than-complete grasp of the domain become impossible to remove due to all the layers.

There is no One Right Answer (tm) to system architecture; it comes with experience. All of these principles should be considered guidelines to help in your design, not ironclad rules.

Except when you will surely need it, just not now, and will have to change your entire core everything to bolt it on.

People saying YAGNI usually make much worse atrocities than the ones aiming for SOLID. And on SOLID, the I alone may be responsible for the overwhelming majority of the problems.

Anyone writing a library should be forced to single step through it in a debugger in front of a group of people and listen to all the grumbling.

The thing is if you have twenty devs all making their code as absolutely simple as possible, the overall system is still gonna be pretty complicated. That’ll be challenging enough. Don’t add your own accidental complexity on. Even if you can fit it in your brain, you’ll be “misunderstood” and “have to” fix things for others all the time instead of working on what you want to work on.

Devil's advocate opinion.

Some might say that the library is poor if you have to debug it at all.

Any library you have to debug to make work is, by definition, a leaky abstraction.

Now, if you followed SOLID principles, you could simply swap the library with the leaky abstraction out with another under a new concrete implementation, verify in tests that you are calling the library with the correct arguments, and your problem should be fixed by only touching the wiring code to point to your new implementation. If it is, then you can deprecate the old implementation class, find all the uses via compiler warnings, and replace them with your new implementation. Then all your tests should pass, and the issue should be fixed.

Accidental complexity arises when you can't do this, and it comes from not properly abstracting away the use of others' code from your own and by overly applying both the NIH and YAGINI principles on both ends of the spectrum.

Simple means "of fewer parts." In maintenance, this means you change fewer parts of your system to make a change. In design, this means you use fewer abstractions. The balance is struck when you have just enough abstraction that maintenance and design are simple.

All software has bugs.

All abstractions leak.

Composition always leads to new problems.

And we are talking here specifically about people using “good practices” in a way that makes the code far more complicated than it needs to be.

The guy I know right now who writes the most tortured code is so stuck on code deduplication that he’s made a mess. Stepping through his code is a recursive nightmare of wiggle words (compounded by his raging overconfidence in his grasp of English). His code has no shape and he likes it that way.

Somebody hurt him and we are all paying for it.

> So glad to see this stated publicly, and actually getting some positive responses. 25+ years of software development and the best "pattern" I can identify after dozens of projects is just one: simplicity.

Yup. So much so that if it takes me several reads to understand code, it's a code smell for me.

“Just because it works, doesn’t mean it isn’t broken.”
Sickle cell trait works to protect from malaria.

It works but is broken.

> I can identify after dozens of projects is just one: simplicity.

Isn't that the point in SOLID though? To try and have some guiding principles for achieving simplicity. Certainly I have noticed that my own code hasn't been easy to understand a few months after writing it, one of the reasons used to be that I was trying to do too many things in one function / method.

Of course having said that, everything is usually a trade off and overemphasizing things will probably be counterproductive.

The other problem with ideas like SOLID, is that they tend to be so abstract that its difficult for them to be meaningful until you have enough experience to not really need them as guidelines.

> Isn't that the point in SOLID though? To try and have some guiding principles for achieving simplicity.

Probalby yes, but some things are an overkill.

"Single responsibility principle" is great, and should be practised.

"Open–closed principle" is also good. No problem here.

"Liskov substitution principle" sometimes feels like it should be more a language feature than an implementation one.

"Interface segregation principle" can be ok. The problem lays on when one creates interfaces for things that don't need one, because they are one of a kind. The code becomes confusing without any real gain.

"Dependency inversion principle" depends a bit on the previous point, because the links are through interfaces.

I've seen (and been charged to maintain) programs with too much ID (of SOLID) done, and it's chaotic.

It makes sense to do them in an enterprise size program, it makes sense to do them as a refactor when the program grows. But people applying principles willy-nilly on programs that do not need them (yet) make it too confusing for the ones after.

This so much!

simplicity is underrated and unfortunately esp. young devs are proud when they are capable to build complex systems.

After working extensively with plugin architectures like OSGi an learning about functional programming I am deeply skeptical when somebody mentions plugin systems and IoC containers.

I've just recently started using Java 8 on the job and what's really jumped out at me is the slight of hand of the Function interface. By simply creating a generic interface called Function and "tricking" developers into coding to an interface things like IOC containers and mocking libraries are no longer needed.

I don't need Spring to auto wire in dependencies in order to simplify testing. In my test class I can just whip up whatever dummy implementation I want and create add a constructor for the Unit Under Test to inject that implementation manually.

The other thing that's been rolling around in my mind using Java 8 with the Function interface is; if you want to expose architecture issues and other warts in the code base, disallow the use of mocking test frameworks.

Had a knee jerk response to this around SOLID really advocating for simplicity in a somewhat rigorous definition.

Completely agree with your statements, a good set of guidelines.

A challenge we face is that there will always be those who either don't know what they don't know so will continue to reinvent the wheel, or those that don't feel at ease unless they are doing something clever and difficult to maintain.

I have seen many talented folks from both sides but they are ultimately a cancer to any product.

Forcing them to maintain their mess has helped but most times it is pawned off.

Transparency, traceability, ownership...that I believe is a path of least resistance that could improve things and lead us towards simplicity.

Rules engines come up frequently in my domain area. What are some good resources for managing either high volume or high complexity business rules?

Usually these start out as hard-coding, then evolve into a rules engine or framework. Sometimes after the original devs leave, the rules engine turns into a shiny black box that remaining devs are afraid to touch.

What are some anti-anti-patterns?

Be deliberate and skeptical about how and why your rule definition language is a better fit for the problem space than a general purpose programming language. When you find yourself recreating a general purpose programming language, stop. Just drop down to one. Or start with one. A very successful rules engine at my employer is Python minus features, as opposed to the typical "config plus features until it becomes a shitty Python."

Realize that what you are doing is a programming language, and create as much of the infrastructure for programming as you can for rule authors (version control, code review, unit testing, incremental deploy, etc).

What about security and safety. A user provided rule in a DSL has controller access to the rest of the environment. While a user provided script in a language will have a lot of security and safety issues, even if you trust the user there is security in depth and safety of limiting the accidental damage
Not sure about Python, but it is very easy to embed Lua in an app in way that executed scripts have access only to what is deliberately exposed to them.
Sandboxing the code is a solved problem, is it not? There are a number of websites that run code for you somehow.
It's a surprisingly tricky problem, btw, at least for some languages. Here's a nice 2014 talk by Jessica McKellar: Building and breaking a Python sandbox that gives insight into some pitfalls. Might be "solved" by now though, don't know.

https://www.youtube.com/watch?v=sL_syMmRkoU

Running stand alone and throw away code in a container, is very different from running a user provided script within your long lived application securely. Think credentials, Db access, file system access, network

But you want to access the DB and write to files and the network just not anywhere, so you have different process and communicate via rpc

I prefer to use SQL because it handles the set definition very cleanly. It is basically the "where" clause of a query. And it's a good middle ground where programmers and business analysts can speak the same language to describe a dataset.

Once the set is defined, the programming language of choice can be used for the action. It could also be SQL, or since we use Spark for a lot of our compute it could be Scala, Python, or Java.

I've been in recent discussions about building a new DSL for this, but I haven't been convinced why we need a new DSL when SQL is already a widely supported DSL.

Bertrand Meyer suggested you split the code for making a decision and the code for acting on it into separate methods. Happens to work pretty well for writing unit tests, too.

If I don’t edit your function, it’s harder for me to screw it up. You can segregate code without pulling it out into an interpreter.

That sounds very much like functional programming styles, where decisions are made by "pure functions" and actions are taken by "interpreters" or "effect handlers". The "ports and adapters" and "functional core, imperative shell" approaches are similar.
Thanks for the lead, will read up on this.

In my SQL biased mind I think of the decision as a dataset defined by some filters and joins. When new data meets the criteria, it triggers an action for that set.

Can I ask a different question?

Very often rule engines are introduced with assumption that "advanced user" with understanding of the business side of the issue would provide (create/modify/remove) set of rules for the system.

More often than not it happens so only devs are able to modify those rules.

So the question is: why is it easier for you to write business logic in some kind of DSL instead of in the actual programming language?

I have never seen this work out as intended - at best it results in developers having to code up things in a limited way through a leaky abstraction. I've never seen users be actually able to use such systems.

The best that can be said is that it sometimes gives the ability to do some customization or extension without having to do a full deployment, but it also locks you into an ever-expanding surface area of code that needs to be supported and increases the chances of bugs from unforeseen interactions.

Constraints. I prefer not having the freedom to do anything I want in some cases. Pretty much the same rationale as the one behind removing the "goto" instruction.
One time we put these rules in a graph database. It worked better than hard-coding and a rules engine.
Ha, funny you should ask... I've started a project about a month ago that will apply various "rules" be applied to unique resource instances. (Sorry, I'm being explicitly vague as there are IP concerns here.)

Here are some approaches that I find myself gravitating towards with this project (but still in design/early dev, and much more to address before this part):

1. Tag-based attributes for resources (as in "meta-data"). Tag combinations can be leveraged to apply specific "rules". But the interpretation of those tags won't be a jump-table or vector-table. If I can't see the domain I'm debugging, neither will the devs who come after me!

2. Taxonomies suck for this kind of thing. Ontologies (what I see as Taxonomies with links) may be required in extreme cases, but just make sure that the full path is always "visible", not just a pointer to node a in the ontology graph. The project I did work on for some time was usurped by another that grabbed my attention (I was re-assigned). Too bad, I didn't get to follow this through. I don't think this is needed for my current project, but time will tell. (I hope not, as it's "elegant" but not at all "simple")

3. State tracking automata may be possible as well. Each request to execute rules stores the path through the rule set directly within the request. Easy debugging and visibility. I've not implemented this type of "rule" system before, but I'd like to give it a shot some day. Of course, writing rule paths is time consuming (even if in-memory) and may be overkill.

4. One (simple) example of a rules engine is a command-line tool's argument parser. Does anyone store this data in a jump-table? I don't think so, (maybe GIT or AWS-CLI, but I don't know as I've not looked yet). Most solutions have a parse pre-step and then a current-state result, usually followed by a "switchboard" type rule applier. But, that state is usually always available; I think that simplicity.

In short, for a given set of "attributes" which drive a "policy", I'd prefer to see the code rolled-out and explicit rather than deferring to some dance between a set of "rule tables" and a set of classes that get invoked in "special" ways.

Of course, I know nothing of your domain; so please take this all with a grain of salt. I've not worked with more than several dozens of "rule sets" before; you may be facing 1000's (and it sounds like speed is critical for you as well). Your rules may be transient, time sensitive and possibly even self-modifying (ugh!). If so, I'd make certain that the path through the rule-sets (if truly required) are well documented and verbose in their logging.

> * AOP for logging; ...

Can you expand on how this works in practice. Or link to an explanation? (I assume AOP means "Aspect Oriented Programming").

Yes, AOP == Aspect Oriented Programming

Logging systems can usually be enabled by package/namespace, and frequently to the class level. I've used AOP to log out the trace of the code (not just the call stack), but can turn it on or off as needed. Authentication acting strange? Enable those logging aspects and browse the log to see where things are behaving strangely, and it may not be in the stack trace of the exception that gets thrown somewhere else. Logging methods and some arguments is hugely helpful; it's like automatic debugging.

A quick googling found: https://www.yegor256.com/2014/06/01/aop-aspectj-java-method-... and https://nearsoft.com/blog/aspect-oriented-programming-aop-in...

Ahh OK. So you mean fine-grained selection and routing of log messages.

I'd never thought of that as AOP, but I suppose it is. And I suppose AOP frameworks provide the machinery to do it, without the logging subsystem having to reinvent those wheels.

I found IOC mostly usefull in my projects. Mostly problem is that when business is saying "we need to have this thing generic", dev team goes ape-shit like public WhateverGoesInAnythingCanComeOut List<T>(List<T> love), where "generic" for buisness people is "copy-shit-paste" and change 2 things.
I find myself somewhat reluctantly agreeing. While I think the SOLID principles and similar things are helpful tools for thinking, I think they should in general take a back seat to the simple principle of Less Code. I’ve seen so many over-complicated code nightmares that were created in the name of “separation of concerns”, or some such, that my spine tingles anytime someone starts talking about some abstract design principles.
> were created in the name of “separation of concerns”

when I hear "separation of concerns", I now immediately ask (internally or externally) "do we need to be concerned about this at all? right now or even years in the future?"

Recent project: untangling something that was written 2 years ago as "best enterprise engineering practices". That dude is gone, no one else on the team understands it because it's insanely over-engineered. This is literally "send a notification email", and there's about 38 core files, another 25 'helper' files, ~20 templates and a custom template parser/nested template thing, and a few dozen node/npm dependencies. This is a 'microservice' running on lambda. literally, someone needs to be able to send a notification that "X is ready", and it's this behemoth that no one understands. They do understand that all the templates and logic were taken away from each caller's context - so all the various teams that used to be in control of their own notification messaging now have to wait for someone to dig in to this other codebase which is basically unmaintained now.

It's got its own custom date parser, because... why not? And there have been random "wrong dates" being sent out for months because... no one can quite trace down the root problem. I finally did, and I'm not saying "no one ever can", but it's under no one's direct ownership, and the people that have tried to take it over have just been overwhelmed with trying to trace through it, make it testable, etc.

And last week I heard rumblings that another team is "rebuilding notifications".

It's as much a failure of management/oversight as it is "overengineering" in general, but at each review step (so I was told), things "looked good", in isolation. No one quite pieced together that this was an unwieldy behemoth until after this guy was gone. :/ (no doubt an extremely typical story, but one which, by now, I would have hoped would be 'less typical').

Beware Chesterton's fence. [1]

Is the network always reliable? Are the messages supposed to be at-most-once, at-least-once, or exactly-once? What happens if power is off when the service is supposed to send the email, and then it comes back: should the email be sent anyway? What if there are five thousand emails waiting to be sent? What if "X is not ready" at the moment, but there's a message waiting to be sent?

A lot of that complication might be because, like Spolsky says [2], it "fixes that bug that Nancy had when she tried to install the thing on a computer that didn’t have Internet Explorer".

[1] https://abovethelaw.com/2014/01/the-fallacy-of-chestertons-f... [2] https://www.joelonsoftware.com/2000/04/06/things-you-should-...

One way to keep this from happening is code reviews. Don't show me a presentation, don't show me blocks of architecture, let's open Github and start looking at the code. Nothing formal, just a 10 minute walkthrough and discussion. Unfortunately in many Eng cultures the EMs stop doing this, or do it too late. In my mind this simple idea is similar to the Toyota Production System's "Go and See for Yourself (Genchi Genbutsu)".
people feel bad about publishing a PR and being told "don't do it like this" and get defensive, argue about the sunk cost, throw references justifying why their approach is Good Actually, accuse the reviewer of not being serious about software engineering, etc.

The real way to project success is egoless programming.

started on this project a few months back and have had PRs blocked with stuff like

"in this test file, just use one assertion, don't need to use two"

"there's too many comments here"

"i don't like the extra line breaks in this file"

"bookAdminId needs to be bookAdministratorId"

these are generally comments just on test files.

I've struggled to deal with these because... well... ego is part of it, but when there are bigger structural issues, and the focus is on - imo - minutia (without much acknowledgement that these are, indeed, minor points), it's troubling. To then be told after a few weeks that you're "not delivering" is doubly so. It's hard to take 'ego' out of this altogether, but thanks for reminder.

Yeah this sucks. the egoless approach is to suck it up and do the changes, but also to raise with the team the difference between feedback for implementing the feature & expressing opinions. One thing I liked about Review Board (!) was making comments as 'Issue' (to make it clear this had to be fixed before merging) vs. not (making it just opinion/non-critical feedback). Though in a team of pedants you would find every comment being marked as an issue
Stuff like this is why I've become a huge advocate for automatic formatting tools. If you can set rules for naming conventions, line breaks, etc. then it significantly cuts down on the minutia arguments.

It doesn't fix everything, of course. Someone can still argue about using too many assertions or comments but it at least helps. If someone makes an argument about formatting then you can just say "well, Tool X formatted it this way so this is how it will be. If we think that should change in the future then let's open a different issue and discuss that."

Man, I feel for you. You could be working with an ex-colleague of mine, from all you've said. I inherited _his_ codebase when he was let go, and had to re-write most of it, there was no way to fix that in a reasonable amount of time, if even at all.

But the same type of things on the PR comments, the over-architected, highly-coupled monstrosity that never quite worked.

In the end, we ended up with 6K fewer lines of code (out of a total of 16K or so), and the thing actually works now. Go figure...

Code formatting tools/linters and static analysis tools can deal with the bulk of this before it even gets to review.

For the small stuff that remains, ideally the reviewer can fix it - the reviewee is probably back in flow on another task, and it's irritating to have focus broken for inconsequential things.

As a side note, I like how github implemented this as "review suggestions", where the reviewer can make small changes inline and the reviewee approves them with a button push.

IIRC, code reviews were done, just... no one really saw the entire 'big picture' and how unwieldy this was until the very end.
I wonder how much of this is driven by the interview culture of forcing people to write complex solutions from scratch in isolation.
After thirty years of software development, the one principle that keeps recurring is Donald Knuth's "Premature optimization is the root of all evil".

It applies on so many levels, including whether to go all in on any software engineering methodology. For instance, applying SOLID principles to your personal side project is counter-productive; but if it grows into a multi-developer project you might well gain efficiencies by rationalising your code structure.

Yep. As another old-timer, pretty much the only principles I have seen that consistently pay off are KISS and YAGNI. And the problem is nearly all of this SOLID type stuff is directly contrary to that.
I've found it important to include the context of that quote:

> We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%.

That 3% can be significant.

Yes, I’ve had to find a new job many times because we waited too long to fix real problems that hampered our ability to make progress.
Sounds interesting, could you elaborate? Why did you have to find a new job? What were the problems that ruined your project?
If my competitor can add features faster than us then they will pull ahead and there’s very little we can do about it.

I’ve been on both sides of that. Sometimes on the same project (quick hacks up front leading to calcification and death).

If each feature takes longer than the last one it becomes hell, and you don’t really want to stick around for that.

I think I agree too. Maybe not disdainful, but certainly skeptical.

I put things like SOLID and design patterns in the same bucket as scalable agile, and similar prescriptive practices. To me, they seem more concerned with organisation as opposed to exposing real business problems so that you can solve them, and as a result you're wrestling with the abstraction and the glue code more than the business logic.

A finite state machine is my favourite example, as I've seen all kinds of 'well-organised' setups that could have been massively simplified as an FSM.

Whatever the right architecture and tech is, I've become more convinced over time that the easier it is to delete bits of your code, the better. And that doesn't always mean you go full-on OOP. More to the point, 'maintainable' is massively subjective. Many of the metrics we use to measure that are utterly shallow.

I agree, mostly.

The more experience I gain, the less I’m concerned about refactorable, local issues. Distributed systems is hard. Scaling is hard. Data consistency is hard. Security is hard. API design is hard. Refactoring a local code base to extract out methods and implement interfaces, much less so.

These days if the biggest problem we have is a monolithic service is badly coded (but is correct and works), it’s something I’ll take.

I too disdainful SOLID. I appreciate this finally has been pointed out. Principles are in fact moral high grounds, without telling you what to do.

For example, you need to resolve the expression problem[0] in Java, with Open-Closed principle compliant. Then the answer could be Java code like in this article[1] (the technique is Object Algebra).

However the moral for me is, if I'm using Haskell or Ruby, when expressing Open-Closed idea is simple, I would use it. If I'm using Java, just forget the principle. I would just express the business, and change the code if I need to add new types or operations. Because the principle is written the code is not understandable to other people, just like design pattern abused codebases.

Another thing is Liskov Substitution Principle. I found most of the books cover this idea don't even mention how to judge if an object is LSP compliant. Every people I know talk about the principle haven't heard of covariant and contravariant, and didn't realize almost all of the mutable data type inheritance are in fact not LSP compliant.

[0] https://en.wikipedia.org/wiki/Expression_problem [1] https://koerbitz.me/posts/Solving-the-Expression-Problem-in-...

I was active in the XP mailing group when things like SOLID got popularized, mainly by consultants. Of course the principles had been around for much longer in various forms. To me, there seemed to be a distinct difference between what some of the consultants focused on, and the books they ended up writing compared to what people who were using XP techniques and trying to leverage (determine) the best software design approaches to write actual production software. Some of the book writers / consultants were much better at actually observing (or having direct experience of) real issues and how to deal with them. But actual software design did not center around things like SOLID, those things sort of just fell out of approaching design in a particular way. I find the language too strong ( laws / principle ). Instead as you find ways to construct modular and composable software, you'll notice that things tend to have a single responsibility, you'll notice that "modules" tend to become stable and extendable through other "modules" of software. They are like side effects of good design, not things to focus on other than if a design is not really working well, it might provide an insight into why its suffering.

Which is all a polite way of saying it is mostly crap and the insight of people with real problems and who dealt with those problems is mostly ignored.

I agree to some extent. I think in general they're good principles, but I think people tend to take them as gospel and forget about code readability and complexity in order to adhere to them. It's important to understand the trade-offs involved, and to understand that different problems will require different solutions.
I wonder if this is because you used them, learnt from them, internalized whatever design intuitions you could from them, and have moved onto higher levels of understanding.

Expert bike riders disdain using training wheels, but only after they've learned how to ride using them.

---

(please hn don't derail with "well actually"s about training wheels)

Yeah

>write code that's easily understood >write code where things are where they're expected to be >write code that can be adjusted and extended quickly >write code that can be adjusted and extended quickly without producing bugs >write code that allows for implementations to be swapped out (think swapping out Email APIs, ORMs or web server frameworks)

The problem is to further these goals they introduce abstraction and complexity which make the code harder to change, harder to reason about, harder to test. And a lot of times you end up taking one step forward but two steps back.

Programming paradigms are like diet plans from weight-loss companies: most of the time they're just the original problem repackaged as a solution--with a snazzy name and aspirational slogans. The difficulty of the endeavor basically get hidden in the contrapositive. "Eating meals that we provide" sounds easy. Not eating what you want to eat--that's the hard part. Writing code that meet certain standards is easy. Not writing code that does not is incredibly hard when have hard requirements and delivery dates.
Agreed. Adherence to dogma like this is cancer.

i was rejected rom a pretty big company after clearing other stages because I wasn't able to recite SOLID. Its like asking people if they know 'design patterns' and expecting them to come up with the textbook answer from the GoF book.

Telling people that you need to use OOP or DI or any fixed software methodology is just stupid.

None of this crap is used in real companies, the good ones anyway. You should be flexible, open, and strive for maintainable, consistent, and simple design.

The most important rule is understanding the context of any design guideline, rule-of-thumb, "pattern" or "best practice".

Something like the Open/Closed-principle is a really great principle for the kinds of projects if was created for. It is just that for 90% of software development it is not appropriate and therefore leads to needless complexity.

Discussing whether SOLID is good or bad in itself does not make any sense IMHO.

Seconding this. I used to really care about code maintainability, cleanliness, and other topics super important to us engineers.

4 years in The Valley have quickly dissuaded me of such foolish naive notions. Your code (outside some common sense basics) is completely irrelevant. Whatever you can stick together with duct tape and chewing gum is almost always the best solution. All the business wants is speed. By the time "bugs" is the highest reason for churn ... well you're likely going to be on your 5th employer by the time your first employer reaches that point.

Absolutely nobody in the real world cares about the quality of your code. They just want to do their job with as little interaction with your software as possible and move on.

It's kinda sad really how much time we waste talking about the best way to hold a hammer, the best hammer you can use, the best nails etc. when all our customers want is the picture to be up on the wall already.

Edit: One important addition -> If you're senior and you see juniors writing "bad code" on your team, your job isn't to teach them why it's bad. Your job is to create systems that make good code easier and more obvious to write than bad code. Be the force multiplier.

Not everyone works in "The Valley".

The vast majority of software is not written with that amount of churn.

The end user doesn't care about the quality of the code, but you can be damn sure they care about the quality of your software.

Also, talking about hammers is overly reductive. Software is far more complex than hitting something with a hammer. Builders might not talk about the best way to hold a hammer, but they constantly talk about the best way to build a house.

> The vast majority of software is not written with that amount of churn.

My current employer has code that is 6 years old. I had coffee with a former colleague and they are still running code from when I was there, and I left 5 years ago.

Code lives longer than you think.

6 years is still young for code. My current employer has code that's 13 years old. My last had code that was nearing 20.

And there's nothing wrong with that. Old code isn't "bad". In fact, it's almost certainly better than new code, because it's been battle hardened. It's got that fix in it for that weird edge case.

The last company I worked for was just trying to move off of a PowerBuilder+Sql Server 2008 system that was written by contractors in 1999 and maintained by two developers who had been there for 20 and 14 years.

What was wrong with it:

- neither the version of PowerBuilder or Sql server was supported by their respective companies.

- they were dependent on the two developers not getting hit by the lottery bus. Good luck trying to hire someone who knew PowerBuilder and/or was willing to learn it.

- the only way to access the program by the remote offices were Citrix Terminals.

I was originally brought in to lead the effort to modernize the system [1] but they had a change of plans when I got there and I ended up leading a completely separate effort.

[1] the plan I proposed was to upgrade to a newer version of PowerBuilder that could expose the system as COM objects, write a C# WebAPI around it. Write automated integration tests calling the Web service and slowly migrating the PowerBuilder code to C# and calling the underlying stored procs directly from C#.

I'm not saying that old code cannot be bad, merely that it's not bad because it's old.

Your approach seems like the right one.

Some code lives longer than you think it will, and some dies really quickly. The problem is that you never know which is which.
This seems related to the problem of how we have no idea how to hire people. I don't want to hire or work with people who are on to their fifth job by the time their crappy work starts causing major problems for the company that paid them to make it. But asking little whiteboard coding questions gives me zero signal to determine that.
It has null to do with "The Valley". A young startup should probably take velocity and "just ship" over any other consideration. But new startups are not a very big or important part of the IT industry.
Have you ever worked on the same project for more than a year?

That's the sort of timescale where you notice quick hacky solutions come back to haunt you later. Sure you will deliver things quickly at first, but when you do have to modify things, or fix your own bugs it becomes a nightmare.

Yeah, been maintaining the same software for 4 years now. Honestly the elegantly designed beautiful parts of the systems have been the hardest to maintain. They end up too specialized to a single use-case and you have to keep rebuilding them from scratch.

Meanwhile the hacky code keeps chugging along happily and dealing easily-ish with any new requirement you throw at it.

What a terrible attitude! You're happy to write shit code because it'll be someone else's problem to deal with by that point?!

I can understand not being dogmatic about SOLID, design patterns and the like, but doing any old shit because you can get away with it is pretty disgraceful IMO. That kind of behaviour certainly wouldn't fly outside of "The Valley".

The customer does generally prefer it if the picture doesn't fall off the wall the moment you leave, though.
This where the "some common sense" basics part comes into play.
Totally disagree and I am rather dismayed by people conflating their bad experiences with people over-engineering with these principles.

They are like anything else a good they are good rules of thumb when scaffolding your projects.

Understanding IoC with DI has cleaned up my code soo much and I know it works as I can Unit Test easily.

Even using the "right" architecture or tech stack doesn't matter that much. All business problems can be solved with any language, with a monolith or a fleet of microservices, it doesn't matter.

What matters is to ship code that brings a solution to the client's problem.