13 ms·
I can tell an amateur programmer from a professional by looking at their order of priorities when they grow a code base. Amateur programmers tend to put code d
by wellpast 11y ago
I can tell an amateur programmer from a professional by looking at their order of priorities when they grow a code base.
Amateur programmers tend to put code de-duplication at the top of their priority list and will burn the whole house down to that often-trivial end.
This writer is pointing out that there are other concerns that far, far trump duplicated code -- and she's right. However she's not elaborating enough on what it is a "wrong abstraction." We can be more precise.
The real offense when we factor duplicated code is the new dependency that is added to the system. And this is what amateurs don't understand. Every dependency you bring into your code architecture costs you and should be judiciously introduced. De-duplication of code ALONE is rarely a strong enough reason to add a dependency.
If you want to be a professional programmer one of the most important things to acquire is a distaste for dependencies. Every dependency you add should be carefully considered (and lamented as a reluctant necessity if you decide to introduce it). As a 20 year veteran in this industry having worked on myriad code bases, I will always prefer a code base with duplicated code and fewer dependencies than the other way around.
So back to the "wrong abstraction". When we compose systems, we are looking for fewest dependencies and stable dependencies. What I think the writer means by "the wrong abstraction" is a "volatile dependency".
I'm trying to be precise here because a common reaction to terms like "the wrong abstraction" is that wrong/right, its all subjective. The truth of the matter is that it's not subjective at all -- the higher-quality system is the one with optimally few dependencies and stable dependencies, these are measurable qualities.
- dustingetz 11y agoThe distaste is for complexity. Adding new functions to reduce duplication can add complexity. It can also reduce complexity. It can also decrease complexity for a while and then in the long run increase complexity. Recall the famous quote, "It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures." Sometimes dependencies are really cheap - when they are the right abstraction. I think the article put it fine. Rails itself is an example of an abstraction that reduces complexity for a while and then adds complexity when you reach a certain size. So it was the right abstraction at first, and then the requirements change and it slips over to the wrong abstraction. Here is an insightful comment about why that is both inevitable and doesn't matter: https://news.ycombinator.com/item?id=11028885 https://news.ycombinator.com/item?id=11028885
- wellpast 11y agoHere's a very objective and powerful way to measure complexity: dependencies and volatility. Otherwise we're all saying "complex" but not being clear and likely meaning different things. For example, a lot of people believe that "not easy" = "complex" but as Rich Hickey articulates that's a counterproductive way to think of complexity. (See http://www.infoq.com/presentations/Simple-Made-Easy http://www.infoq.com/presentations/Simple-Made-Easy)
- dustingetz 11y ago"dependencies and volatility" But what does this even mean? I'm okay with using Rich Hickey's definitions. But I don't recall that in Rich's talk.
- Anchor 11y agoIf your system's design results in your stable components depend on the non-stable (volatile) components, your system is complex. This is because volatile components change often and these changes ripple to your stable components effectively rendering them volatile. Now the whole system becomes volatile, and the changes to it become very hard to reason about - hence complex. Avoiding this problem has been captured, among others, by the Stable Dependencies Principle (http://c2.com/cgi/wiki?StableDependenciesPrinciple http://c2.com/cgi/wiki?StableDependenciesPrinciple), which states that the dependencies should be in the direction of the stability. A related one is the Stable Abstractions Principle (http://c2.com/cgi/wiki?StableAbstractionsPrinciple http://c2.com/cgi/wiki?StableAbstractionsPrinciple), which states that components should be as abstract as they are stable.
- radicalbyte 11y agoSandi, the writer, is a she ;)
- wellpast 11y agofixed!
- tel 11y agoI understand and sympathize with the idea you suggest here, but I also wonder about the fine tuning. We accept that both duplication and dependency are bad (and certainly that the minimal, maximally stable dependency set is best) but when making that tradeoff what parameters make a duplications better or worse than a dependency? Are there languages or toolchains which cause this tradeoff to fall in the opposite direction? In some sense this is academic, but in a very real sense disdain for dependency is something I worry can prevent a project from going through an important high-energy transitory period where semi-formed dependencies exist to solve concrete tasks but have not yet annealed into a final, low-energy form. In small scopes a professional knows how to skip by this risk and get straight to the better abstraction. In large scopes whole projects must (often) pass through high-energy areas. Professionalism thus demands that these high-energy zones be identified and reduced. But being too eager to avoid dependency might inhibit growth.
- wellpast 11y ago> In some sense this is academic, ... Whether all of this is academic or not, I don't know. But what I do know is that these ideas and their practical implications have an ENORMOUS impact on the practitioner's and business's productivity. > in a very real sense disdain for dependency is something I worry can prevent a project from going through an important high-energy transitory period where semi-formed dependencies exist to solve concrete tasks but have not yet annealed into a final, low-energy form. We must always riff and hack and creatively explore our domains in code -- this is another practice of the software professional, and notions of "architectural soundness" and "dependency reduction" should never paralyze us from creative play, sculpting and exploration. In these modes of development its best we "turn off" all the rules and let ourselves fly. But for a code base that has to survive longer than 6 months and that will have more than one collaborator -- this is where it becomes essential to maintain architectural soundness in the shared branch. (My development branches, on the other hand, are in wild violation of all sorts of "rules" -- so there is a difference between what gets promoted to production code and all the exploratory stuff we should also be doing.)
- tel 11y ago
- MrRage 11y ago> I can tell an amateur programmer from a professional by looking at their order of priorities when they grow a code base. > Amateur programmers tend to put code de-duplication at the top of their priority list and will burn the whole house down to that often-trivial end. To be fair, lots of how to program books spend a lot of time on teaching you how to abstract, and sing abstractions praises to the heavens, as it were. It takes some experience to learn that real world stuff is not like the toy programs in books.
- dakotasmith 11y agoTo be fair, working only from book knowledge and no experience is precisely what makes someone an amateur.
- Jtsummers 11y agoNovice, not amateur. Amateurs aren't paid, professionals are. Novices are inexperienced, journeyman and masters are more skilled and experienced. A novice can have a ton of knowledge (from books), but be too inexperienced to apply it. A novice can be a professional, this is what internships and entry-level jobs are supposed to be for. Paired with mentorship and structured work assignments (structured in the sense of increasing complexity, scope, and responsibility) they're brought up to journeyman and, later, master level. They can also be amateurs. Given forums, books, manuals, mentors (real-life or online), they can be brought up to journeyman and master level as well.
- dakotasmith 11y agoI was following usage as it was in the thread, though I agree that amateur is being thrown around this thread where novice is the word implied.
- Jtsummers 11y agoYeah, sorry. I realized after I posted that I should've put this upstream.
- exelius 11y agoThere are also different kinds of dependencies. Some dependencies are so common, they might as well be a feature of the language itself (Google's OAuth Java client libraries or Apache HTTPClient, for example). It would be a waste of time to write a custom OAuth or HTTP library - much better to just add the dependency and go on your way. IMO the danger is greater with other languages that don't have the library support that Java does. That Ruby Gem you use today may get abandoned in 6 months and become a piece of technical debt you have to deal with later down the line. It's always a balancing act. There's also nothing inherently wrong with duplicated code. It can make a code base harder to maintain, but so can over-abstraction. But I agree with you and the author; most projects are better off when developers err on the side of verbosity.
- wellpast 11y agoThose dependencies you mentioned are all highly stable. That's the objective & precise way to say that they are "the right abstractions" [1]. [1] This should further articulate why "right/wrong abstraction" is not the useful nomenclature. I bet you I could find several programmers who could write a "better"/"more good" OAuth library, but that's not what is of priority here. What is most important is this objective quality: is the dependency (it's interface) stable? If it is, then my code architecture remains sturdy.
- dozzie 11y agoOf course there are different kinds of dependencies. I believe it was not the point of wellpast that dependencies are evil and bad, I think it was that dependencies have cost, and most the time programmers consider them as (almost) free. Indeed, dependencies have nearly zero immediate cost, but in the long run they are expensive and need to be weighed against the gain from their use.
- reacweb 11y agoI think the first priority should always be simplicity (KISS). Simplicity is understood by perfectionist developers (Simplicity is the ultimate sophistication) and also by beginners (if code become complex, there will be bugs). De-duplication (DRY) is important but should always come after simplicity.
- bograt 11y agoI broadly agree with this, but draw a slightly different conclusion: that the imperative when growing a large code base is proper tooling/planning for dependency management. If good dependency management is cheap, programmers will use it, and even if some dependencies are volatile, side-effects can be bounded by the dependency graph.
- dozzie 11y agoWe have ~good (and certainly cheap) dependency management in Ruby (gems) and JavaScript (npm), and it leads projects straight to dependency fractal. No, it can't be bounded by making it easier.
- Anchor 11y ago> The real offense when we factor duplicated code is the new dependency that is added to the system. A slight tangent on that note, but I think many of the problems with current web development result from the same root cause: adding yet another dependency to solve an almost trivial problem. Sometimes going to the extreme of for the sake of saving few keystrokes. Need one function to find an item in a collection? Reference Lodash. And then drop references all over the place. Yes some dependencies are useful, but they all need to be handled with care. Wrap that search operation to an internal utility function and inject Lodash as its implementation if you don't want to reinvent the wheel. This is what DI is for (and nothing more). Maybe the feedback of exploding the compile times as a result of a complex dependency graph would make people more sensitive to the issue. But then again, that did not prevent it from happening either. Oh well, it's not my codebase (yet).
- ubercore 11y agoI think javascript is intrinsically going to be one of the worst examples of that. Stemming back to the days when javascript programmers learned that using a library like (then jQuery) lodash is the _only_ way to correctly get your code to work in all cases. That culture then became ingrained, and in my opinion helped lead to the state most JS packages are in these days. In my experience, this is one of the greatest things about the new prevalence of tools like babel; it pushes that abstraction layer down below in-code dependancies. There's still a dependency to manage, but it's not a library import or similar.
- habitue 11y agoBabel doesn't solve the problem of having a minimal standard library. Unlike say Python or Ruby, where batteries are included, JS is roll it yourself, add a dependency, or go without in almost every case. Even for seemingly trivial things like "Array#contains"
- ubercore 11y agoIt may not help that case in particular, but there's a _lot_ that ES6/ES2015 add to the table: http://babeljs.io/docs/learn-es2015/ http://babeljs.io/docs/learn-es2015/ A lot of things in there would need a library for cross-browser compatibility still. That's my point, but I take yours as well. It's still a very BYOL (bring your own library) kind of a language!
- elcapitan 11y agoIf this is mostly something amateurs do, then who are those people that bring us all these wonderful abstractions like AbstractClickableLittleButtonWithTinyImageOnTheLeftSideFactoryControllerStrategyImplementation?
- ThrustVectoring 11y agoExpert amateurs.
- SideburnsOfDoom 11y agoThere's even a slogan for that: Beware the Share http://programmer.97things.oreilly.com/wiki/index.php/Beware_the_Share http://programmer.97things.oreilly.com/wiki/index.php/Beware...
- curun1r 11y agoDependencies (coupling) is an important concern to address, but it's only 1 of 4 criteria that I consider and it's not the most important one. I try to optimize my code around reducing state, coupling, complexity and code, in that order. I'm willing to add increased coupling if it makes my code more stateless. I'm willing to make it more complex if it reduces coupling. And I'm willing to duplicate code if it makes the code less complex. Only if it doesn't increase state, coupling or complexity do I dedup code. The reason I put stateless code as the highest priority is it's the easiest to reason about. Stateless logic functions the same whether run normally, in parallel or distributed. It's the easiest to test, since it requires very little setup code. And it's the easiest to scale up, since you just run another copy of it. Once you introduce state, your life gets significantly harder. I think the reason that novice programmers optimize around code reduction is that it's the easiest of the 4 to spot. The other 3 are much more subtle and subjective and so will require greater experience to spot. But learning those priorities, in that order, has made me a significantly better developer.
- Anchor 11y agoI think those four criteria beautifully capture many goals of software design. And for example, we could say that we reduce system state with a functional approach, make component coupling looser by indirection with object orientation, and decrease code complexity by structured programming.
- wellpast 11y ago> I'm willing to add increased coupling if it makes my code more stateless. I like statelessness as a top priority. However I'm not sure how statelessness ever comes into tension w/ coupling. Aren't they mostly orthogonal concerns? > I'm willing to make it more complex if it reduces coupling. Complexity = f(Coupling), in my definition. So an increase in coupling results in an increase of complexity. Sounds like you have a different definition of complexity -- I'd love to hear it.
- curun1r 11y agoThere's a few ways in which state vs coupling can play out. Often they're part of the architecture of a system rather than the low-level functions and types that a developer creates. As an example, should you keep an in-memory queue (state) of jobs coming into your system or maintain a separate queue component (coupling). By extracting the state from your component and isolating it in Rabbit or some other dedicated state management piece, you've made the job of managing that state easier and more explicit. As for complexity, there are many different types. Coupling is a form of complexity, but it's not the only one. Cyclomatic complexity is another and one. Using regular expressions often increases the complexity of code. And one need only look at the spec for any reasonably popular hash function to see a completely different sort of complexity that's not the result of either coupling unique paths through code. The composite of all the different forms of complexity is how I'd define it since they all add to a developers cognitive load.
- sbov 11y agoOff the top of my head, there are two reasons people seem to de-duplicate code. One is because two or more things happen to share similar code. Another reason is because two or more things must share similar code. It seems like you are speaking of the first reason. There is no dependency, and the programmer is creating one. IMHO you should have at least 3 instances before creating an abstraction to reduce your code. The second reason is different though. By creating the abstraction you are not adding a dependency, you are making an implicit dependency explicit. There is a huge difference. In this instance any duplicate code is bad.
- Chris2048 11y agoI think that principle I've heard before called "1, 2, 3, abstract"; as in, wait until you see it at least three times before considering extraction. I'd also add - wait until the code is 'stable', i.e no longer under active architectural development, connected only to other stable parts (or with stable/authoritative interfaces) and having then existed in such a state for a continued period of varied usage. then refactor.
- Anchor 11y agoI'd say this is sound advice for core components of the system, but we may want also to consider the types of the dependencies. For example, I would not wait for three times if the dependency is some kind of external system (DB, UI, Network, MQ, OS, Library, Framework, or the like) which is volatile in one of the worst ways - not directly under your control, unlike your own source code.
- eva1984 11y agoAgree. Frequency matters. If it is used everywhere, no reason not to abstract.
- runT1ME 11y agoI have a third reason. Abstraction, or code de-duplication, if done right, ends up making code easier to reason about. The paper that made me rethink everything I knew is http://www.mpi-sws.org/~dreyer/tor/papers/wadler.pdf http://www.mpi-sws.org/~dreyer/tor/papers/wadler.pdf and explains this wonderfully.
- Almaviva 11y agoI can tell an amateur programmer because they aren't getting paid. Flippancy aside though: While I don't think it's what you're arguing for, I'd fear some would use an argument like this to defend a workflow and culture where new features are started by copy and pasting a mass of code, and then going in and tweaking little things here and there. Then when something has to change across the system, there are endless traps because things you need to change and maintain, that look identical, aren't quite. These workflows and cultures exist. There's a balance somewhere and finding that is the hard part, right?
- collyw 11y agoThats what I was thinking reading the article.
- shasta 11y agoBut all we have to do is teach your half-baked "fewest, most stable dependencies" theory to the amateurs and then they'll be professionals, performing like 20 year veterans. Eye roll.
- jdmichal 11y agoAdditionally, what I also see that tends to separate out experienced vs inexperienced programmers is attempting to deduplicate things because they appear similar. By this, I mean that these two things just happen to do similar things or have similar fields, but the operation or structure that they are attempting to represent is fundamentally different. When they eventually diverge, because they were different operations or structures, so they won't evolve the same... This is where you end up with functions that take 10 different option parameters, all of which activate a different code path. Or structures with completely different fields set depending on where it came from. And guess what? Now you're back to basically one code path or structure per use case, and an extra dependency that didn't exist before and is a nightmare to maintain without breaking one of those existing use cases.
- fao_ 11y ago> This is where you end up with functions that take 10 different option parameters, all of which activate a different code path. Or you could abstract the commonalities out into a higher-order function.
- jdmichal 11y agoWhat is the higher order function for two disparate code paths, controlled by a parameter switch? `if`?