17 ms·
Solid Principles: A Software Developer’s Framework to Robust, Maintainable Code
- maximus1983 7y agoThe author uses the more complicated definitions of the SOLID principles. These are much simpler to remember: * Single responsibility principle - A class should have only a single responsibility, that is, only changes to one part of the software's specification should be able to affect the specification of the class. * Open–closed principle - Software entities should be open for extension, but closed for modification. * Liskov substitution principle - Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program. * Interface segregation principle - Many client-specific interfaces are better than one general-purpose interface. * Dependency inversion principle - One should depend upon abstractions, not concretions. A lot of these principles also tie into other principles such as DRY (Do not repeat yourself).
- CodiePetersen 7y agoThat is the cleanest clearest collection of solid definitions I have seen.
- hasseio 7y agoThey come directly from Robert Martin's 2000 paper, 'Design Principles and Design Patterns'[1] [1] - https://web.archive.org/web/20150906155800/http://www.objectmentor.com/resources/articles/Principles_and_Patterns.pdf https://web.archive.org/web/20150906155800/http://www.object...
- maximus1983 7y agoI stole them from Code Project :D
- hideo 7y agoMy 2c to teams attempting to write SOLID code: Please make sure that the entire team that touches the code base understands what makes it SOLID-compliant (Don't like the term compliant, but it'll have to do). SOLID is more a framework for communication between engineers than anything else. For example, if one engineer thinks class X has a single responsibility, but the name can be potentially interpreted differently, then unless she's on the job and reviewing every commit the principle will soon get violated. However if the team is open to having a healthy debate and iterating until they mostly agree, then SOLID raises the team's understanding of their code and THUS the quality of the codebase.
- cgrealy 7y agoSome constructive criticism: abstract class Employee { // This needs to be implemented abstract calculatePay (): number; // This needs to be implemented abstract reportHours (): number; // let's assume THIS is going to be the // same algorithm for each employee- it can // be shared here. protected save (): Promise<any> { // common save algorithm } } class HR extends Employee { calculatePay (): number { // implement own algorithm } reportHours (): number { // implement own algorithm } } class Accounting extends Employee { calculatePay (): number { // implement own algorithm } reportHours (): number { // implement own algorithm } } class IT extends Employee { ... } This is still a violation of SRP. IT does not care about the employees pay and shouldn't be forced to implement it. Modern OO adds an additional rule to SOLID: favour composition over inheritance. HR and IT should not extend from Employee. HR should be more like class HR { calculatePay (Employee e): number { // implement own algorithm } reportHours (Employee e): number { // implement own algorithm } } The first question you should ask when deciding to use inheritance is: do I really need inheritance here? Then, ask yourself again: Is <childClass> a <parentClass>? In most businesses, HR is a Function or maybe a Department. HR might have Employees but HR itself is not an Employee.
- jpitz 7y agoThis is a big part of Liskov substitutability.
- Supermancho 7y agoEvery employee needs to be paid, even if that's 0. The original class was correct (a class is a namespace, then functionally dependent on language implementation). The implementation of calculatePay, is extensible as it stands. public Number calculatePay (Interface department) You delegate to the class (even if the interface is optional) Number val = defaultPayscale(); if(department) { val = department.getPay(); } return val; Because EVERYONE expects Employee to have a calculatePay of some sort, for accounting software. The idea that you want to smear a concept across domains for a misguided utopian design is over-engineering. Programmers have an average intelligence, so aim for supporting a simpler mentality, regardless of your beliefs.
- UK-AL 7y agoSome of these code examples are not great. Using inheritance when it's really not needed.
- maxxxxx 7y agoThis looks to me like the usual textbook examples of inheritance that usually fall apart quickly when they get in touch with reality.
- risaacs99 7y agoThe 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.
- ctvo 7y agoI 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.
- Swizec 7y agoSeconding 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.
- 7y ago
- revskill 7y agoCode is not that matter. It's the database design. The understanding of domain that reflects in your database design. Another way speaking, given me a well designed SQL database, i could produce a working software in one day, with bad code even. Most of broken code is because of wrongly designed database, not the code itself.
- whycombinater 7y agoI second this. I seventy this. The bottom layer, designed inconsistently, will percolate through until some wrapper code renormalizes any inconsistencies. Layers and layers of junk built on junk.
- deleted 7y ago[deleted]
- BeetleB 7y agoNot to take away from your comment, but you do realize a huge amount of SW doesn't have any SQL database?
- revskill 7y agoSQL is just one example of Database. My comment is generic about storage implementation though. The same story applies to NoSQL or any other kinds of database as well.
- redact207 7y agoThese principles are the first thing thrown out the window for new apps. I've been developing for around 15 years and I've noticed the excitement of new devs on new projects who'll rush out and just throw something together quickly to solve the immediate problem. I've done this a lot too. This gets you a long way very quickly, which is proof that none of these principles really matter. Development continues along this path at a cracking pace for 6 months until complexity spikes and your productivity is at a crossroads. Do you take a step back, refactor, restructure so the code can be built on with any degree of velocity? Hell no! Everyone's expecting the same output as when you started, so now you have to start copy/pasting everywhere trying to keep up, but your bug tickets have started ticking in so you have to address those. But you don't have tests, so now each bug fix raises two more bug tickets. Uh oh. Better get more devs. Now you have people who don't understand the system making changes. There's no real structure so they just squeeze code in where they can, but progress is slow because it takes them five times the amount of time to reason about what the system does and bugs are still going out. Must be bad developers. Better hire a QA team to stop all that. QA is reporting defects with each release candidate, and puts it back in the dev queue. After a year each feature is taking months to go out. The business just wants some simple changes, why is that so hard? Enter the hotshot dev. They whip up a quick MVP with the latest language/framework plus it's serverless! No SOLID principles but that doesn't matter because the latest technology will fix everything. Better fire the old team and start fresh.
- alexvaut 7y agoAgreed 100%, I worked with legacy code at different stages: 1 to 2 years: very little of structure but still velocity matters a lot, complexity is not very high; 2 to 5 years: first customers. copy/paste designs and complexity arise; 5 to 10 years: I observed 2 different trends, software where refactoring happened, product is still not in a very good shape but it is still ok and the others, the garbage product with millions of lines: global variables, If/then/else copy/paste design, doesn't scale, broken everytime for no obvious reasons. It's going to take years to stabilise without too much churn. So everytime I'm working on a project, I pledge managers, devs to allocate time to refactor and follow those patterns, implement unit tests etc... everytime at whatever stage, after sometime the team realizes the benefit. So yes, without a doubt, good design patterns are keys to make our dev lifes as easy as possible. And the SOLID ones are definitely my favourites.
- Izmaki 7y agoI believe you got the D wrong. Dependency Inversion is not just about slapping an interface in between two components. It's about inverting the dependency. Let me explain: Picture that we have two components, a main component "Master" and a subcomponent "Slave". What you seem to be saying is that we should have an interface between the two so that Master uses an "ISlave" interface that the Slave maintains (the developers of the subcomponent) instead of using Slave directly. Dependency Inversion however dictates that you should invert this dependency so that Master dictates which functionality Slave should implement. That is, Master should have an ISlave interface (or an "IDelegation" or similar new name) that Slave (!) is required to fulfil. There is a small but very important difference between the two directions of the dependency arrow. One of the main points is that with Dependency Inversion it is the main component that is in charge of functionality instead of the main component having to work with the functionality exposed by the subcomponent.
- s188 7y agoNice. That's a really useful way of looking it it. I'll definitely commit that to memory.
- CodiePetersen 7y agoI must confess I am really bad at sticking to this. Sometimes I find myself writing a clunky class or function that is doing too many things and I say to my self "You're a dumb ass you are going to have to rewrite this." I just need to practice holding my feet to the fire and doing it. There are some things that I do regularly as habit, but occasionally I find myself in golden hour mode ignoring all the good practices. But I suppose as long as the refactor/rewrite follows good practice its probably not too much of an issue. Although my one complaint is the very unhelpful acronym at times.
- zmmmmm 7y agoI have to confess, I have never been able to figure out what the Open Closed principle means. I've never seen an explanation that made sense. Extension vs modification? What's the difference? Is it just a very confusing way of trying to express the idea of encapsulation? I guess I am plagued by the knowledge that I have virtually never been able to predict accurately what "extensions" will want to be done to my code in advance. So much so that I consider attempting to do it now a fairly pernicious form of over-engineering.
- namelosw 7y agoAbout the Extension: Think about the concept of '+', you can '+' integers, rational numbers, matrix, etc. There possibly thousands of kinds of things that could be '+'-ed. (Actually, this concept is called monoid) Your job is to implement the abstract idea of '+' (the operator itself without specific meaning) and the integer implementation for '+'. Then you can compile publish your package to a jar, dll or any other form of package other people can download. Everyone, possibly thousands of people can download this package and extend and use the exact same '+' from you, for their own datatype without modifying your code or recompiling your package. At last, 99 out of 100 this could be over-engineering for most of the languages like Java. But for language like Haskell have a feature named type classes, make this almost no-brainer, so it would become 5 out 10 this could be over-engineering in Haskell because the cost and mental overhead are reduced.
- charlieflowers 7y agoEssentially, it means that you can extend something by writing _new_ code, in a _new_ file, rather than modifying some code. For example, a case statement cannot be modified without adding a new case. But something that finds all objects which implement IFoo and uses them can be modified by implementing a new implementer of IFoo. (That's not the only and probably not the best example, but it is an example). A lot of people don't realize that most (maybe all) of these principles originated on C++ code bases way back, where changing one file in one tiny way could cause massive number of files to need recompiling -- which at the time was very painful due to slow compilers. They have some good applicability, but I think they're massively over-sold.
- deleted 7y ago[deleted]
- mannberg 7y agoOne alternative to learning these principles for solving problems that arise when you wrap all your logic in objects, is to actually not wrap all your logic in objects.
- s188 7y agoAmen
- jondubois 7y agoFor OOP, I aim for: - Separation of concerns between classes/modules. The responsibilities of a class should be easy to explain to a non-developer. E.g. Ideally , every class/module abstraction should be an entity discovered in the specification (or easily inferred from it) and not invented from the developer's own mind. - Be very careful about how you name things. Try to stick to the spec/domain terminology.
- hyperpallium 7y agoA program is a theory. A theory can be evaluated: firstly, does it describe/predict the data (i.e. does it work)? Secondly, is it parsimonious? But the key issue with a program-as-theory is that you must understand the theory to understand the program. Because of this, it may be much more effective to shoehorn a problem into an ill-fitting theory, if that theory is already well-known and well-understood. A new, unfamiliar and strange theory must be REAL GOOD - and that improvement must also be valuable in the applied context - for it to be worth using. If so, it becomes a new standard theory.
- SergeAx 7y agoI am surprised by a number of people here with a lot of relevant experience and opinions like "to hell principles and patterns, just make your software simple". Most of the software just cannot be made simple. I'll go further and say: you cannot make life with simple software, because there are a lot of players around who will take your simple software and make it into more profitable complex software. SOLID is essentially a way to make complex software simple to understand. Yes, you have to write 5 classes to introduce new type of persisting entity, but they are simple classes, mostly boilerplate. Complexity is not in number of LOC or files, it's in business processes, their interdependencies, "when this then that, except those" and so on. With SOLID (coupled with DDD) your hiring process becomes quite simple: you check basic knowledge of language, performance tricks and caveats (like n+1 problem), understanding of principles with examples and voila, you have new team member. On the first day you will show him or her your domain dictionary, give access to git and ticket system, and you will get good enough code to fix bugs or introduce features in a matter of days. And it doesn't matter if your code base is 10 years old, if you followed those principles yourself for all that time.
- redact207 7y agoI'm a DDD convert, but boy do I still have trouble selling it. "why do we need a ubiquitous language?" "why can't I jam everything into a single domain?" "why can't everything be an aggregate root?" "It's too complicated, why can't I just PATCH my data directly into the database from API?" Honestly for me it's these abstract principles and practices that outweigh any flavour of the month technology. DDD encourages code to model the business domain, meaning the complexity should equal that of the business. A lot of problems I see with apps are due to reducing complex domains into CRUD ops, then trying to build the complexity on top as an afterthought. Also, a DDD helper package for node - https://github.com/node-ts/ddd https://github.com/node-ts/ddd
- dep_b 7y agoI really can't write code other than a) experimental (when testing a new library that encompasses a totally new concept) or b) absolutely readable and SOLID. a) code never makes it to production before it became b) since when I understand the problem and my solution I can't leave dead or unreadable code. I've seen people that write nice code by day check in horrible entangled vomit because they wanted to make a deadline. So the next time I pull it, "it has just a few issues" and it takes me two days to sort out their mess where just writing it cleanly myself might have cost me only one day. My code just looks the same no matter what pressure. I just don't believe I go faster by making code harder to understand and maintain.
- cjfd 7y agoI am not a big fan of 'Solid'. Some of them are true but of the category 'water is wet'. I.e., the SRP principle. Others don't seem useful. The open-closed principle, for instance. All software should be adaptable and we generally don't know what requests are going to come from the field so we cannot write parts of software to never be changed. Perhaps you think that bool is_prime(int n) is never going to change but someday somebody is going to request that it can report progress during its calculation. So really, you can never know. Also, these things with dependency injection and interfaces. Please, only introduce these complications when you can prove that you need them. The software will be complicated enough when it is satisfying all its requirements. It is unnecessary to invent complications for no reason.
- croo 7y agoIf SRP is akin to water is wet I'm working with newborns. What is so trivial about what to do in a class? Deciding where to put what is one of the hardest part of the job...
- cjfd 7y agoWhat to do in a class may not be entirely trivial but also not that complicated. What is trivial is noticing that a class is doing too many things. The problem is actually doing something about it. This seems in many case to be more of a discipline problem than it actually being that hard. I also have seen that people are trying to do too much. When one sees a problem many have the inclination to want to refactor everything at once and also redesign an entire program. A more productive way is to do one small refactoring that solves just one code quality issue at a time. Before you know it you have done 5 or 10 small refactorings and things are starting to look much better. The big refactoring is very likely to make as many things worse as it makes better. The small refactoring has much more chance to have only or mainly positive effects. Sometimes one also has to take a step back and think about in what direction to go with an application. That can be a bit hard, indeed. Even in that case it is much better to do small steps in the right direction that one has envisioned rather than some bing bang rewrite.
- juliangamble 7y agoThere is a fantastic talk by Kevlin Henney where he deconstructs the SOLID principles as each being either misunderstood when they were written - or frequently misunderstood after. It was done here: https://vimeo.com/157708450 https://vimeo.com/157708450 I won't spoil it by writing all the points here - just some pearls. * The Open/Closed principle is redundant because it is already covered by the Liskov substitution principle. * Bob Martin's view of the Dependency Inversion principle is in fact the Single Responsibility principle. He suggests 5 more principles - but I won't spoil it by listing them here. Go see it!
- codr7 7y agoThere's just no way to win this game, whatever good ideas are thrown in will be twisted, turned and corrupted into worse than what came before. I once worked on the software side of a 500-employee electronics company that had managed to ISO-certify their so called Agile development process. Everything, and I do mean everything; was performed according to some ISO-script. No deviation, no matter how meaningless the task. And we had more useless meetings than anything I've seen before, it's just that they were called stand ups and reviews. Thanks, but no thanks.
- p0nce 7y agoThis article completely changed my mind about SOLID and I realized how underrated this simple mnemonic still is: https://www.gamedev.net/blogs/entry/2265481-oop-is-dead-long-live-oop/ https://www.gamedev.net/blogs/entry/2265481-oop-is-dead-long...
- mxcrossb 7y agoIt seems like 90% of this can be achieved for free using a language with duck typing and named parameters. But of course we know codes written in those languages are often not robust and inmaintanable. What is missing? Is it the static typing? Or that all of these goals are implemented mindfully? Maybe the key is in how you document the principles.
- planxty 7y agoI suspect the majority of people who claim to disdain SOLID principles are in fact using them every day without realizing it. Seems pretty disturbing that so many engineers are piling on to recommend abandoning adherence to some really basic (and easy to understand and apply) ideas about design. Glad I don't work with you! :-P
- reallydontask 7y agoI think a lot of people have seen that SOLID principles sometimes actually have the opposite effect of their intention, i.e. they make the code hard to reason about and thus less maintainable and harder to extend. There are quite a lot of articles out there with valid criticism of SOLID, you might find that you don't disagree that much after all (or not). At the end of the day, blind adherence to dogma will likely result in negative effects. If strict adherence to SOLID principles works for you, then great. Not been the case for me.
- Izkata 7y ago> I think a lot of people have seen that SOLID principles sometimes actually have the opposite effect of their intention, i.e. they make the code hard to reason about and thus less maintainable and harder to extend. For me, it's half that and half that the people most likely to promote it - like in this blog post - are doing it badly. For example, their very first one, the Single Responsibility Principle, isn't. It's blind adherence to a code smell at the syntactic level, resulting in subclasses that are closer to god objects than single-responsibility. IMO, extracting "Payments" into its own class/module is the "right" day to do it, whether or not the switch statement is kept or a method is created for each Employee type.
- deleted 7y ago[deleted]
- ideal_stingray 7y agoIn the first example, isn’t it kind of a problem to have a bunch of different functions for calculating pay? Seems like it would be easy for the accounting department to end up with a different version than the HR department that actually disburses the money, which would cause all kinds of hell when trying to audit your accounts. If one team or department owns the function, you end up with more interdepartmental communication if another team needs to change it, but you won’t end up with Accounting and HR disagreeing on how much employees should be paid.
- jonathanstrange 7y agoHere are two concrete problems that these general design articles never answer: 1. How to deal with global dependencies like logging, internationalization, application "constants" (not always constants in the programming language), preferences, ...? If you inject these as dependencies into every module or package, then you end up with a convoluted mess of initialization parameters and dependencies. If you make them globally accessible, re-usability is lost almost totally. In both cases, the code depends on the preference system and the particular values of preferences. (In a real-world application almost every "constant" has to be parametrized as a preference.) 2. Closely related to this, how do you deal with miscellaneous data that any real-world application needs, but that pollute and "impurify" your objects and classes. Typical examples of this second category are binary data from rich text and other potentially volatile and framework-dependent data---often, but not necessarily only GUI-related. If your application is database-driven, you have to store this data, which usually means you also have to store it in a class or struct in your programming language. For example, you might not just need to store an employee name, you might also need to store employee name richtext data, employee name display flags and filter category information, and so on. Trying to strictly separate model data from view-related data basically just gets you twice as many classes and tables for a little bit of gain in "purity." That is true even if you follow a strict MVC pattern (which I usually do), and in the end you store all of the data in the same database file anyway. I'm currently using Go, but the same problems also came up in Racket for in the past. I've never read any software design article that provided concrete solutions to these problems. I wish those articles would stop discussing toy examples about cars and employees and instead explain how real-world problems are solved.
- mdpopescu 7y ago1. Decorators. See https://blog.ploeh.dk/2010/04/07/DependencyInjectionisLooseCoupling/ https://blog.ploeh.dk/2010/04/07/DependencyInjectionisLooseC... 2. Yes, you need a lot of model types (groups, layers). You need the view models, the database models, the business logic models, and possibly others. Sure, that's annoying at first because they're pretty much identical. That's fine though, because in that case you can just use AutoMapper or something similar. I have never seen a project where the various models didn't change independently in a short timeframe (weeks, months at the most).
- dirkg 7y agoDoes SOLID align with functional programming? I've come to think that IOC/DI should almost be an anti pattern as it depends on interfaces and I've never seen anything use it that wasn't abstraction hell. Functional programming seems like a breath of fresh air, which is ironic since it predates all this modern buzzword centric world and was around with LSIP.