23 ms·
My favorite principle for code quality
- sp332 8y agoRobert Piersig illustrates these higher-level concepts, which he calls forms, Pretty sure that's attributed to Plato. Well I guess he called them είδε but he couldn't wait around for English to get invented.
- aero142 8y agoI think that was one of the great things about Zen. It was an unreliable narrator who had megalomania. At one point, he acknowledges that most of what he is stating is pulled from older greek philosophy that he didn't bother to learn properly but then continues to claim to invent new philosophy through the story.
- SilasX 8y agoI get the feeling that most fans of Zen didn't interpret the narrator as unreliable :-/
- mlthoughts2018 8y agoI feel like there must be an equation such as: Peter Principle + Embedded Design Principle = Conway's Law
- trhway 8y agoseems the article ends up with what was first done decades ago in PHP - separation of presentation and business logic. Btw i don't understand all this animosity toward PHP, especially given that pretty much all the modern web development is basically PHP-like, in spirit if not in actual implementation.
- icedchai 8y agoMost PHP is actually the opposite... Many PHP scripts, especially those from decades ago, are a mix of HTML, database calls, and business logic all in the same file.
- flukus 8y agoAnd now we have JSX and this is considered good again.
- jstewartmobile 8y agoJumble code and markup together in PHP, and you've only got one language to put on your resume. Jumble code and markup together in JSX, then you can put JavaScript, ECMAScript, React/Vue, npm, node.js, babel, and possibly webpack on it.
- kevincennis 8y agoCan you explain what you mean by "pretty much all the modern web development is basically PHP-like"? I'm not necessarily disagreeing. I just don't really get what you mean by that.
- vorpalhex 8y ago> i don't understand all this animosity toward PHP Most PHP code is terrible. That's not really PHPs fault - it's capable of producing very nice programs with beautiful behavior. As a language, it suffers a bit from being older and it was internally very inconsistent for a long time. For a long while, it's package management and practices were way behind the curve. Many people who program in PHP learned it as a first language, hacked together programs in it, and probably used hack together libraries and tooling. PHP can be used with grace, but most people don't have that experience with it.
- yani 8y agoFor what use case is PHP better than other languages?
- jstewartmobile 8y agoRapid development of performant get/post web interfaces. Ruby is nice but runs a lot slower. Python is better in a general-purpose sense, but every file will have a preamble of includes for things that are just built-in to PHP. As a general purpose language, PHP is weak. As a web-form DSL, it's great!
- farrisbris 8y ago> Many people who program in PHP learned it as a first language, hacked together programs in it, and probably used hack together libraries and tooling. This describes me very well, and i don't think i'm alone. When i look back at my(very early, first language) PHP code it looks horrible. A lot of it is just copy pasted code from random websites telling me that that it does what i wanted it to. Which is a pretty good description of me at a young age trying/learning to program by internet. I don't use PHP today, and i really don't want to, but i suspect that is in some part due to me struggling with stuff i never understood when i was doing it and developing a distaste for it looking back at my bad code from that time without realizing it could be done differently/in a way fairly close to what i do in my current work-language. I just wanted to make my WoW dkp page work and searched the internet when wondering how to and ended up writing really bad code. It still worked pretty good and i had fun doing it but i never considered writing PHP again once i moved on.
- yani 8y agoJust give yourself time to write and maintain PHP code for over one year and you will find the answer. PHP does not have a debugger nor a test runner nor a profiler built in. Someone will say use xdebug and phpunit and etc.. well I want tools that ship with the language and are not slow like phpunit...
- everdev 8y ago> The Embedded Design principle, which I briefly introduced in a previous post, states that you should always code in a way that makes the design apparent. It’s like pixie dust in its ability to make code more readable, loosely coupled, and impervious to bugs. I'm all for good software design, but claiming a design process can eliminate bugs is more fantasy than reality. Bugs typically come from unforeseen requirements or application states. Yes, planning reduces bugs, but bugs are still inevitable no matter what software design process we use.
- extrapickles 8y agoI agree with you. Issues with communication were directly attributable to ~1/3 of the bugs in software I've worked on. These bugs are unfixable with any process as they are caused by not stating a requirement/feature/bug in a way that is understood perfectly by the developer writing the code.
- krona 8y agoYes, you can't prove a negative. I'm pretty sure it was a joke.
- deleted 8y ago[deleted]
- hinkley 8y agoThe experience of fixing a bug somewhere in a Ball of Mud is profoundly different than fixing it in better factored code.
- codeflo 8y ago> I'm all for good software design, but claiming a design process can eliminate bugs is more fantasy than reality. Bugs typically come from unforeseen requirements or application states. In my experience, that's only true code that's relatively well-engineered to begin with. Unfortunately, there are lots of bugs in real-world shipping software that have nothing to do with an unforeseeable environment, but are only the result of unnecessarily complex designs that even the original programmer hadn't completely thought through. So while good design might not eliminate bugs, bad (or no) design can definitely create them.
- et1337 8y agoI prefer a more focused version of this principle: you should organize your data in a way that makes the design apparent. The code will follow later. This principle still works with the author's example. Most of the refactoring he did boiled down to modeling the data more accurately.
- kochthesecond 8y agoI also try to stress this principle. Data and its structure is the most important part, and bring accurate and explicit, lets the author communicate their design better than mountains of doc. Or inversely: if the data presented are complex and convoluted, you will need mountains of docs to try explain its design.
- lttlrck 8y agoAlgorithms + Data Structures = Program This was something that was stressed by my mentor, who was a mathematician turned programmer and a big fan of Niklaus Wirth. It has proven valuable to me on numerous occasions and informs design. https://en.m.wikipedia.org/wiki/Algorithms_%2B_Data_Structures_%3D_Programs https://en.m.wikipedia.org/wiki/Algorithms_%2B_Data_Structur...
- joslin01 8y agoI feel like this is a long-winded way of saying what Linus already has famously said: "Bad programmers worry about the code. Good programmers worry about data structures and their relationships." The author refactors some code to revolve around a "DashboardStat" and explains it came from "thinking about how the code is derived from the design." Personally, I think it came from putting the data & its structure first.
- gowld 8y agohttps://softwareengineering.stackexchange.com/questions/163185/torvalds-quote-about-good-programmer https://softwareengineering.stackexchange.com/questions/1631... gives some of the genealogy of the idea. http://www.faqs.org/docs/artu/ http://www.faqs.org/docs/artu/ "Rule of Representation: Fold knowledge into data so program logic can be stupid and robust." https://users.ece.utexas.edu/~adnan/pike.html https://users.ece.utexas.edu/~adnan/pike.html "Rule 5. Data dominates. If you've chosen the right data structures and organized things well, the algorithms will almost always be self-evident. Data structures, not algorithms, are central to programming."
- kqr 8y agoI agree with the general sentiment, but suggest exercising caution: Every problem can be solved by an additional layer of abstraction, except the problem of too many layers of abstraction!
- gitgud 8y agoIs that a quote from someone? It nicely sums up my naive experience of trying to "clean up" my code
- j_tomanik 8y agohttps://en.wikipedia.org/wiki/Fundamental_theorem_of_software_engineering https://en.wikipedia.org/wiki/Fundamental_theorem_of_softwar...
- sseth 8y agoThe version i like goes something like this : Any problem of abstraction (in programming) can be solved by adding a layer of indirection. Any problem of performance can be solved by removing a layer of indirection.
- vorpalhex 8y ago> Standard disclaimer: When reading software design advice, always imagine the examples given are 10x longer. Overengineering is bad. But, if you’re not sure whether applying a technique to your code would be overengineering, error on the side of doing it. Abstract early. Please, please, please absolutely disregard this advice. More errors, pain and suffering come from early abstraction and poor understanding then not enough abstraction. Build simple, extend as needed. It's a 'frggin stats page where a developer forgot to remove a call. It could of been written much cleaner initially, but breaking it into a bunch of classes at the initial stage is fruitless abstraction.
- sharpercoder 8y agoThis boils down to code removeability. A few larger classes wit large methods is in general easier to remove/refactor then a large amount of small cohesive classes with an obtuse design concept behind it. Of all the codebases I refactored, I take the "large methods and large classes" hands down. Pulling a few classes here and there is easy, compared to first understanding a clusterf*ck of overengineered abstractions.
- ljm 8y agoI worked with a domain driven setup at one point, which seemed like it was designed to sell JetBrains licenses because it became almost unbearable to try and maintain the codebase with a text editor. You would have to go through a controller, a DI container, a repository, an entity, a factory, a builder, maybe a facade, and an event bus... almost all of which were single-method classes (except for the DI boilerplate which was split between constructors and YAML files) that just called the method of the next dependent class. One line of concrete business logic hidden between half a dozen files full of architecture. The rationale was that the abstraction was necessary to make things easier to replace if they weren't needed but it was a false assertion on two levels (and it almost always is): - that kind of replacement is unlikely to happen in the short/mid-term, and if it does it won't be in a way you anticipated. - the simple version could be deleted and rewritten in less time than it would take to fix your highly abstracted/decoupled/meticulously architected integration Of course, simple isn't easy and this approach to abstraction (where you take classes/methods longer than x lines and extract them into more classes and methods) is very easy to achieve... at a great cost.
- dmytroi 8y agoThe post indirectly suggest creating domain specific language for solving dashboard presentation problem. In my opinion this shifts engineers focus to solve problems with-in provided domain specific language, which in the end resolves in job security, as non-senior engineers will not "dare" to touch "the framework". Whole situation, in my opinion, results in "everything working on paper" (aka tests, good design, etc) while in reality nothing works and as project progresses engineers get a feeling that "nothing can be done". I would just remove "numWords = countWords();" line and move on. There is no need to invent domain specific language when just programming language is sufficient. I would love engineers to be more reasonable and aware of DSL's trade off.
- jpochtar 8y agoI believe the code presented would have benefited from a lambda-based API approach instead of an object-oriented one. In Javascript: // global, since the cached values were apparently global in the original let cache = {}; let lastCachedTime = 0; // Utility for fetching potentially cached values, or computing them // if there's no valid cache entry. let cached = (id, recompute) => { // I don't love this policy, but thankfully it's all in one place. if (lastCachedTime <= lastMidnight() || lastCachedTime <= lastNoon() { cache = {}; lastCachedTime = Time.now(); } if (!(id in cache)) { cache[id] = recompute(); } return cache[id]; }; // now we get to have a nicely factored, simple, conceptually independent, // declarative code for the dashboard. function displayDashboard() { print("Total Users: " + cached("numContent", => countUsers()); print("Articles written: " + cached("numArticles", => countArticles()); print("Words written: " + cached("numWords", => countWords()); } This achieves the same "Embedded Design" goal. If you squint, it's actually virtually the same as the DashboardStatComputation/DashboardStat/Dashboard the author comes up with, where each lambda in the displayDashboard function is corresponds to a DashboardStatComputation. I prefer this lambda-based design because it reifies the "form" into a single, callable, function: cached(). If you want to use the idea of cached(), you don't have to subclass anything or know about its implementation details— just call it. (PS. the code's even nicer in CoffeeScript)
- trias 8y agoI agree. Using the class approach usually leads to complex class hierarchies and a lot of made up concepts for simple things which would be better solved if you could pass functions around easily (lambdas) and had generic data types (like javascript objects). But java does not have these easily available. I think the OPs problems are mainly coming from the culture/design of Java.
- zackbloom 8y agoYes, 90% of abstraction is best done by organizing files and functions, NOT by creating classes.
- mark_edward 8y ago
- erpellan 8y agoAsk yourself what assumptions you are baking into the design. Can you make them more explicit? How likely are they to change? Where multiple approaches are on the table, favour the ones with the fewest assumptions. Be especially critical where cardinality is concerned. It's much easier to go from n to n+1 than from 1 to 2.
- deleted 8y ago[deleted]
- antirez 8y agoI can't disagree more with this post. One should always aim at trivial code, but as things get more complex, it is there the the sensibility should fire in and make you say, ok I need more abstraction now, and refactor accordingly. But even then it is important to just add the minimum amount of abstraction to generalize the problem at hand, without thinking like "but why if in the future we change things..." Unless it's very clearly going to happen soon.
- SNACKeR99 8y agoI find this true, and depressing at the same time. The amount of curation needed for a constantly evolving code base is vastly underestimated by most, even to achieve the modest design goals (IMO) that you are espousing.
- antirez 8y agoYep, you have to babysit the code forever or it dies...
- m12k 8y agoI agree, generalize too early, and you are just as likely to overengineer, anticipate changes that never come (YAGNI), constrain the code in other ways that turn out to be unwieldy and incur the mental overhead of a more complex design that each new reader of the code needs to understand. Generalization should mostly happen during refactoring.
- auxbuss 8y agoThere's an excellent discussion on this topic in the sample chapter, Rediscovering Simplicity[0], from Sandi Metz's book, 99 Bottles of OOP[1]. I often use it as a source for discussion with devs. [0] https://www.sandimetz.com/99bottles/sample https://www.sandimetz.com/99bottles/sample [1] https://www.sandimetz.com/99bottles https://www.sandimetz.com/99bottles
- ScottBurson 8y agoI like this, though I think anyone who mentions SLoC as a metric is honor-bound to quote Dijkstra: My point today is that, if we wish to count lines of code, we should not regard them as “lines produced” but as “lines spent”: the current conventional wisdom is so foolish as to book that count on the wrong side of the ledger.
- gHosts 8y agoHmm.. Grrumble. I think he has the tail of something.... but hasn't stated it well. Personally I keep coming across code where the "design" as indicated by names and comments indicate one thing.... but the fact of the code indicates another design. Now if this indicates a bug, ie. Incorrect behaviour, yup, fix it. But usually it has been "tested into the shape of a product". ie. The names and the comments and the data structures indicated first, draft, incorrekt, thoughts... and the reality of the code _is_ the correct behavior. Usually at that point I refactor to reveal what the design really is, down at the mathematics of the code, not what the designer thought it was.
- ljm 8y agoI love articles like these because every time I'm naïvely expecting to learn something new, but more often than not I'm left unsatisfied. It's always the same pattern and it's like the programmer's version of the one weird trick. Show ten lines of (contrived) code and talk about its potential to be flawed, and then 'fix' it by explaining 50+ lines of code that has to be abbreviated or split into pieces to keep the article flowing. What quality actually means in this context is unclear, but it feels like tidying your bedroom as a child: hide the clutter in various places to give the appearance of cleanliness, while actually creating more of a mess for the next cleanup in the process. And like many programming maxims, advising to abstract early is just as bad as advising to not abstract at all. Better abstraction will become more apparent as you see how your system fits together and how parts of it can be lifted up to a higher level, and this kind of intuition will only come with patience and experience.
- deleted 8y ago[deleted]
- keithnz 8y agoThis was a pretty bad article on achieving code quality. First, there was a bit of bashing of design patterns and refactorings and says we can forget all that in order to make it sound like he was going to drop some magic fundamental wisdom. Which he then says is "Embedded Design Principle". Then he proceeds to describe an evolution of a piece of software into a bit of a mess, then proceeds to solve it by "AND here is a design that solves it". No method or process for getting to that design, just "here it is!" .... drop the mic, exit stage left. So basically his advice is dont write crappy code, some how magic up a design that might or might not be robust for the future that reveals your design. Looking at some of his other stuff, he uses a similar "strawman" ish approach to first highlight some technique like (TDD), come up with some crappy code, and use it as some kind of justification for what he thinks. Funny thing is, the stuff he trivialized actually are exact micro techniques that allow one to create code thats shows its design. However, for this article, there is a simpler idea that is useful when tackling code like this ( I first came across it in martin fowlers UML Distilled ) and that was the idea of "Levels of Perspective". Which there is Conceptual, Specification, and Implmentation. Since his code seems to have purposely mushed all those things together, its an easy way to look at this code and make it better. I won't go into the exact details, but given a piece of crappy code either in the beginning stages or even the end stage and start decomposing it, first seperating specification and implementation, then as one gains insight, seperating things into composable conceptual ideas. To achieve this, you want to use things like TDD, Refactoring, Patterns, but, and the real trick, only as is demanded to shape the code into a simple design that is modular and composable.
- sbov 8y ago> “There’s a bug over there because you re-used lastCachedTime. Otherwise, looks good. Ship it!” The fixed code still has a bug. It assumes successive calls to lastMidnight() within the method will always return the same date. But presumably, if partway through the method it ticks over to midnight, that would not be the case. So some stats would be updated during one call, and the rest would be updated during the next call. That would violate this requirement: > They had previously decided all stats should refresh simultaneously;
- lnanek2 8y agoHe does point out every good programer knows they should have one if check for all three: " Wrapping Up . Many programmers will get a sense that something was off about the first example. Every programmer I’ve shown this example to knew they should merge the if-statements and factor out the prints. " Which corresponds to the one if part of the story: " if (lastCachedTime <= lastMidnight() || lastCachedTime <= lastNoon()) { numUsers = countUsers(); numArticles = countArticles(); numWords = countWords(); lastCachedTime = Time.now(); } " So having the multiple ifs being bad even before a second cache refresh is added is part of the story.
- deleted 8y ago[deleted]
- perpetualcrayon 8y agoI find it extremely disconcerting that so many software engineers don't follow this. You're not there to figure out some clever way to do something that you made up. You're first and foremost there to figure out the essence of the thing itself and to find where the design exists naturally. Only then can you really start to design things well IMO.
- mikekchar 8y agoMy advice for design: Design is hard. Good design is so hard that you will almost never see it in the wild. It becomes even harder when you work with other people. Your carefully crafted ideas will be crushed under the foot of the next confident programmer who thinks they have found the holy grail of design. As more and more programmers are added, they pour in their particular favourite flavour of programming sauce. The result is swamp water. Good design is not about the code. Good design is about the people who are going to work with that code. Understand your audience. Get to know what they like and what they don't like. Write your code in a way that will delight your audience. Give them what they expect to see. Of course, this is exceptionally shallow advice because no programmer can actually be so selfless. If you write code that pleases your audience you will usually have to do so at the expense of yourself. It is not possible to write good code that you dislike -- at least not long term. So the trick to good design is always putting a bit of yourself in the code and always putting a bit of your colleagues in the code. It's striking that balance where you are satisfied, but also where they are satisfied. This requires working in small increments, and sharing what you are doing. It requires watching what your colleagues are doing and looking for opportunities to borrow. It requires questioning, evaluating, praising, explaining and coaching. And if you are working by yourself: do whatever the hell you want. Why are you so worried about what other people think? If you are trying to improve what you are doing, don't do so by reading about the opinions of others. Read code, not blog posts. Write code, not design diagrams. Work slowly, try out ideas you've seen in other code and evaluate their success. When you get the chance, work with others to broaden your horizons.
- sooheon 8y agoI like the way you think, do you write anywhere else? I'm only in my first year of designing and writing code professionally, much of what you point out about this being 1. difficult, and 2. a social/human endeavor rings true.
- mikekchar 8y agoI have promised to, but have not followed through with that promise. One of these days maybe...
- pnj9 8y agoMore often than not AwesomeSauce will end up SpectacularFailure, thus making the exercise in divining the future use case of 30 lines of code pointless
- flukus 8y agoThis is a very complicated solution for a problem the compiler would warn you about (unused variable).
- formatkaka 8y agoAbstract Early? That's kind of Anti-KISS . It's better to start simple and abstract when desired. It wastes a lot of time when I try to abstract early. You can't solve a problem without all the variables.
- dsego 8y agoHow does the final example solve the daily slowdowns?
- davidhyde 8y ago"Abstract early" is just plain bad advice because you usually don't know the full problem up front. Better advice would be to write code that lends itself resilient to refactoring (i.e. it is easy to refactor and won't break in unexpected ways when you do so) Additionally, the authors "better" design now abstracts the primary performance problem out of sight of the developer. The main problem I see is that they are making multiple passes through the data to extract different metrics. By abstracting early they miss the opportunity to improve the performance by calculating all the metrics in one pass. The new design has hidden the root cause of the problem and prevented a developer from fixing the core problem without chucking out the entire design.