10 ms·
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
by Jupe 7y ago
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.
- jlj 7y agoRules 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?
- samcodes 7y agoOne time we put these rules in a graph database. It worked better than hard-coding and a rules engine.
- closeparen 7y agoBe 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).
- zaphirplane 7y agoWhat 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
- adrianN 7y agoSandboxing the code is a solved problem, is it not? There are a number of websites that run code for you somehow.
- FabHK 7y agoIt'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 https://www.youtube.com/watch?v=sL_syMmRkoU
- 7y ago
- mcnichol 7y agoHad 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.
- arvinsim 7y ago> 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.
- hinkley 7y agoAnyone 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.
- jackcviers3 7y agoDevil'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.
- hinkley 7y agoAll 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.
- adrianratnapala 7y ago> * AOP for logging; ... Can you expand on how this works in practice. Or link to an explanation? (I assume AOP means "Aspect Oriented Programming").
- Jupe 7y agoYes, 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-logging.html https://www.yegor256.com/2014/06/01/aop-aspectj-java-method-... and https://nearsoft.com/blog/aspect-oriented-programming-aop-in-net-core-and-c-using-autofac-and-dynamicproxy/ https://nearsoft.com/blog/aspect-oriented-programming-aop-in...
- adrianratnapala 7y agoAhh 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.
- collyw 7y ago> 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.
- JaumeGreen 7y ago> 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.
- kstenerud 7y agoSOLID 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.
- marcosdumay 7y agoExcept 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.
- verinus 7y agoThis 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.
- 0x445442 7y agoI'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.
- ozim 7y agoI 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.