There is 3 levels of programmers.
Level 1 is the beginner. A level 1 programmer is barely able to make a complete app or lib. His work simply rarely works. The code has no sense, is full of bugs, it's a complete mess, and the user has a very poor experience (if an experience at all).
Then comes the level 2 programmer. The novice. The novice learnt about OOP, design patterns, DRY, single responsability principle, importance of testing, etc. The only problem with the level 2 programmer is that he overdoes everything. He is victim of overengineering. He is able to make a complete app with a decent UX and the quality of the code is dramatically better, but his productivity is low. Really, really low. Everything takes month. Every single change is a project. 10 actual lines of codes need to be changed? That's 10 classes, 250 lines of unit tests, it requires 3 classes to be refactored, and on and on.
Finally, there is the level 3 programmer. The level 3 programmer is a programmer that does level 2 programming in a smarter way. His first principle is less code. That doesn't mean the code must be complete shit, but the level 3 engineer understand the real enemy and that all problems grow with it exponentially: code. The level 3 programmer knows, understand and apply the principles of the level 2 programmer, but he just does the right amount of them. Not too much.
If he has to, the level 3 programmer gonna err on the side of the level 1 code and will avoid as much as he can level 2 code. That's because level 1 code cost less to fix than level 2 code.
Now here comes my point: a level 2 programmer read and write articles that is about how to not be a level 1 programmer.
I've been doing this recently and had a hard time justifying it even to myself. It just felt right. And you've got it - it's easier to fix dumb code, than refactor an overengineered mess.
> Now here comes my point: a level 2 programmer read and write articles that is about how to not be a level 1 programmer.
You're making me understand the current programming "scene" much better, especially the plethora of self-aggrandizing blog posts I tend to see today.
I wish I could upvote you twice.
I disagree that level 1 programmers necessarily produce poor code. But they're stuck at such a low level of understanding of abstractions that they either abuse abstractions to hell (but not in a level 2 sense) or don't use them at all.
The good level 3 programmer understands that there are times to call a function, times to create class hierarchies, times to create generators, times to use high level abstractions. But they generally err on the side of lower abstractions, or higher abstractions only where they aid clarity, reduces true redundancy, or similar.
But the most important thing about a level 3 programmer is that they aren't dogmatic. Dogma both frees and constrains the developer. It frees them from the requirement to think, because dogma says "thou shalt" and "thou shalt not". It constrains them because when things fall outside the dogma, they want to make the heretical code conform, or they discard it and start over.
Very True!
I once worked with about 1.5 million LOC worth of "naive" PHP code. It was one big e-commerce system. The SSOT principle wasn't followed, certain assumptions were scattered all over the place.
For example the fact that a product could only ever belong to a single size group. The task we were given was to remove this constraint. It took a few man-years of work, because it was hardcoded EVERYWHERE, from database scripts to frontend JS. And don't even get me started on how many bugs angry customers encountered before it got kind of stable.
It wasn't overengineered, just dumb, and a lot of it. I'd much prefer having to deal with some matryoshka OOP abstractions - they could be merged, simplified etc. In such case there are certain tricks in our disposal. Tricks that scale. If it's just a big ball of mud: not much you can do. It's pretty much a physical job, like counting grains of sand on the beach.
If you want to increase flexibility later, like "remove the constraint that a product could only ever belong to a single size group", it inherently means to hunt down all the places where this assumption has been hard-coded.
Should you err on the side of flexibility or simplicity? Simplicity means the assumption hunt is costly, but maybe you never need it (YAGNI). Err for flexibility, you pay the constant cost of dealing with the API complexity, but you surely avoid some costly assumption hunts.
Usually, startup prefer simplicity because they will pivot anyways. In contrast, enterprise prefers flexibility, so they can plan years ahead and things stay in the budget.
Knowing the future in this case should usually mean that you have requests for a feature from customers, but you don't have time to do it in this version. At least that is something I find useful as a rule of thumb. If that is not the case, you should really think twice before making it flexible rather than simple.
That really resonates with me.
I was recently passed off a project from someone who didn't have a development background but had been working on C++... the amount of copy-paste, repetitive if statements and fixed length arrays makes the original code almost completely unusable and I was forced to suggest a total rewrite.
You can refactor as you go. It doesn't save you from sifting through the entire ball of mud, but at least it means you won't have to do it again in quite such a bad way.
A good, easy to understand codebase is one that has a minimal number of components that interact with each other in a well-organized and easty-to-understand manner. Those components, in turn, should each be made of a minimal number of sub-components that interact with each other in a clear, well-organized manner. And on down until you hit bedrock. Doing this is essential because it sets up boundaries that limit the amount of code and interactions that you need to juggle in your head at any one moment.
A programmer who doesn't understand this will produce horrible, tangled, tightly-coupled code regardless of whether they're doing it with a boiling soup of global variables or a boiling soup composed together from tiny "well-factored" classes that all implement interfaces containing only a single CQRS-compliant method.
I'd submit that there's another level of programmer, let's call it the SubGenius programmer (because the term tickles me) who thinks that levels 1, 2 and 3 programmers all share a sloppy work ethos and need to spend more time stepping back to keep an eye on how the forest looks instead of obsessing about trees all the time.
Since ageism is a hot topic now: This is one point where experience pays off. The graybeard has acquired a better gut feeling when something is necessary and when it is not.
The level 2 programmer knows that the tomato is a fruit. The level 3 programmer knows that it is not a good idea to put it in a fruit salad.
The vast majority of programmers are male, so (ignoring that he can be used in a gender neutral way) it's natural to use he in such a situation. I'm not saying it's right or wrong -- I'm not sure what influence using gender-specific pronouns has. Although I will say that if someone used she when discussing a field that is dominated by women, like veterinary science, as a man I wouldn't feel excluded or put off from exploring the field if I had an interest in it. It's also interesting to note that the same furor over gender inequality in subjects like computer science isn't replicated when the shoe is on the other foot.
I think there is a tendency in western culture at the moment to confuse factual differences in behaviour between men and women with ethical issues about equal opportunity. Equal opportunity doesn't necessarily result in a 50/50 gender split in all fields. Christina H. Sommers puts it better than I can: https://www.youtube.com/watch?v=l-6usiN4uoA
Also, I think this was kind of apropos. Seems like the English language isn't using the right levels of abstractions. There should be a true gender neutral pronoun, one that should be used when the gender specificity isn't relevant and should be hidden away behind the pronoun's abstraction. Its just not something we're familiar with, and that's really the only problem. Its hard to force language on people, when everyone learns language effortlessly as they grow up, nobody is used to putting effort in how they talk.
I don't think that's the only problem here. Consider this story: http://www.foxnews.com/opinion/2016/08/03/student-facing-50-...
These sorts of situations are a direct result of the battle "to force language on people", and they are having a big effect on our society. Sethi paid a high cost for a single tweet that wasn't intended to be malicious in any way. And people who read such stories note this, and as a result feel like they must tread on eggshells, because, even though they aren't racist or sexist or any other ist, one small slip up may result in their entire education or career being put at risk.
A populous that is scared to say anything is much easier to control. And I think the powerful are going to get what they want with this. In a couple of decades, I think free speech will be a distant memory, and people like yourself will be questioning the future you helped bring about.
: that person
No, grammatically, "he" always has masculine gender [0]; its historically-accepted (though increasingly-less-so) semantics include use in reference to a person of unspecified (socially-ascribed) gender (classically, use of personal programs in English maps best to socially-ascribed gender, which has not taken gender identity much into account -- recently, there's been a move to align socially-ascribed gender with gender identity, but pronoun use basically follows the former which just happens to have a growing norm of also aligning with the latter.)
> "They" is plural
"They" is grammatically plural and gender-neutral, but has a very long history of accepted use with semantics of referring to an individual of unspecified (socially-ascribed) gender. This acceptance was somewhat reduced by the Victorian fad of Latin-inspired prescriptivism in English, but this reductions is among those of that fads effects that have been fading over recent decades.
[0] Note that grammatic gender is a distinct concept from either socially ascribed gender of a person, gender identity of a person, or biological sex of a person.
Given an HN reader took the trouble to email me their thanks for my comment, I respectfully disagree. To do this, they had find my email address by following some of my other comments, linking to a website, following through to github... The email they wrote was articulate. They put in real effort to say "thanks".
> "he" is a gender neutral pronoun when referring to a person of unspecified gender (as in other languages).
In some dictionaries, yes. A possible counter-argument to this is that the tradition of that usage comes from cultures with significant inbuilt misogyny.
> "They" is plural.
Sometimes. - https://en.oxforddictionaries.com/definition/they - http://www.merriam-webster.com/words-at-play/singular-nonbin...
> "Ve" is not a word.
I can read it, write it, say it and find other like usages in numerous places. To me, that reflects most of the necessary facets of "a word". - https://genderneutralpronoun.wordpress.com - http://vevemvir.tumblr.com - http://www.aleph.se/Trans/Cultural/Art/eganrev.html - http://www.dictionary.com/browse/etymology - http://www.wikihow.com/Create-a-Made-Up-Word
> "S/he" is awkward and unnecessary
Agreed. I dislike this form. A similar option is to alternate use of "he" and "she". This form is common, and probably the simplest. I wish I'd suggested it.
> we can argue that it is misandrous because you're capitalizing the "S" and prioritizing "She" over "he"
That was you, not me.
Singular "they" is a widely recognised usage.
And the idea that "he" is gender neutral always seemed to me like a post-rationalisation for people's androcentrism than any kind of well founded rule.
To defend my point a little: I invited them to consider their use of language, not tell them how to live their life.
I can tell you how I came to this view: I read around through philosophy, pop-psychology/self-help, nlp, linguistics, religion and science-fiction; I spoke with feminists; I spoke with trans-folk and their admirers; I looked at my assumptions as a younger person and found them wrong.
I agree with you, but it feels a little like a false dichotomy. You can pretty easily decompose a long function into multiple smaller ones, within the same file. ~40 lines is close to the breaking point IMO. If it grows much beyond that, you can probably see groups of 10+ lines of code that have some sane subset of inputs/outputs. Toss those lines in another function, give it a name, make sure it's not exported outside of this file. If the 40+ line function gets reduced to a 10-20 line one with a few more stack frames, it is probably worth it.
If it's 100,000 lines of code, and you break stuff every 40 lines, you have now introduced 2500 procedures many of which don't really need to exist. But because they do exist, anyone who comes along now has to understand this complex but invisible webbing that ties the procedures together -- who calls who, when and under what conditions does this procedure make sense, etc.
It introduces a HUGE amount of extra complexity into the job of understanding the program.
(Also you'll find the program takes much longer to compile, link, etc, harming workflow).
I regularly have procedures that are many hundreds of lines, sometimes thousands of lines (The Witness has a procedure in it that is about 8000 lines). And I get really a lot done, relatively speaking. So I would encourage folks out there to question this 40-line idea.
See also what John Carmack has to say about this:
http://number-none.com/blow/blog/programming/2014/09/26/carm...
I think also what you're talking about is a function of programmer skill. I think if you have a good programmer write a 1000-line procedure, and a bad programmer write a 1000-line procedure, you are going to get drastically different things ... just like with anything.
A good example of a 1000+ line function I've written for business applications was for processing JSON for the initial state of a web app. You have a lot of data coming in, you need to do a lot of verification and transcribing backend data structures to frontend data structures. It's easier to do it all in one place than it is to break it into many little functions that only get called once anyway.
How long does it take you to write and test all those tests? Could you have been doing other things with that time? At 40 lines of functionality, the tests are going to be at least as big as the things you are testing (??), so what kind of a multiplier are you taking just on lines of code written? How much does that cost?
[I run a software company where I pay for the entire burn rate out of my own pocket. So these questions are less academic for me than they are for many people.]
I estimate I write about 2:1 unit tests to code in terms of tests to functions but tests should be quite a bit faster to write than the code they're testing. I think I'm at the low end of how much I test my code compared to other engineers, however.
Perhaps it is different in game development. One of the big advantage of writing tests is that you can aggressively refactor with confidence; if you're planning to stop improving your codebase once the game is released maybe this isn't an issue? Plus bugs are perhaps less of an issue if you inconvenience the gamer rather than lose someone cash, and maybe you aren't expecting to hand code over to new developers.
We test the hell out of our stuff, and it works way more reliably than most web sites I have ever seen. But we don't do it with unit tests, because unit tests are not very useful in complex systems, because they do not test anything hard!
The idea that unit tests are not very useful in complex systems is very controversial and goes against established best practice in software engineering, the advice of pretty much every authority in the field and empirical studies.
Only testing hard stuff is only half the battle. Unit tests also test basic assumptions and have other benefits like documenting intent. See /Code Complete/ for evidence for the need for multiple angles of testing.
switch(JSON.someparam) {
case A: //code to translate A_server -> A_client
case B: //code to translate B_server -> B_client
...
}
Check all your cases have breaks? That you didn't accidentally introduce a new variable into your scope or clobber a variable already in scope?My experience has been that people on HN tend to interpret that part of the posting a little more extrapolatingly than I do. I think he is saying something pretty obvious, which is that when you can structure things in terms of pure functions, you don't have to worry about the side-effects that are one of the main issues you need to contend with when factoring things apart.
This is different from being a "fan of functional programming", i.e. believing you should use current functional programming languages to build your projects, or whatever.
Usually I just search for the name of the puzzle I want to edit (which is also how you'd do it if it were a ton of different procedures).
/**
* get the leaf node as a string
* @param obj the json object to operate on
* @param path the dot path to use
* @return the leaf node if present <b>and textual</b>, otherwise null
*/
public static String leafString(ObjectNode obj, String path) {
JsonNode leaf = leaf(obj, path);
return leaf != null && leaf.isTextual()
? leaf.asText()
: null;
} The KISS principle states that most systems work best if they are kept simple rather than made complex
Therefore, simplicity should be a key goal in design, and
unnecessary complexity should be avoided. YAGNI is a
practice encouraging to purely focus on the simplest
things that make your software work.
made me think that it clearly warns against overengineering.(edit:formatting)
One thing is code execution paths for later debugging. All else equal, code is simpler if everything you need to figure out what something does is in one place. Basically, minimizing the distance you need to travel in order to get related information. Keep variables closer to where you use them, etc.
There's some programming language specific stuff. A for loop that does a mapping is more complicated than calling a map on the collection. More generally, a program is simpler if it uses more specific abstractions, rather than using a very general abstraction in a highly specified way.
Likely, there's more than I'm completely forgetting but will recognize as general principles that I use.
Who was right?
[NB Obviously, I was right, but it would help if there was some objective measure of complexity to justify it ;-)]
1. Clearly map the code/functions to the specs of what it does almost line by line.
2. The code is expressed in a way simple enough for one person to understand it and verify it by hand.
3. The code is expressed in a way simple enough for a machine to verify it should someone want to try.
4. Minimal to no global effects/state happening in the local code.
These principles tend to result in code that's correct and easy to modify. I say they're a start on some objective measure of simplicity. We could empirically [dis]prove them as well with tests of various coding styles on people and tooling.
Blindly following anything -- especially nuanced, process-driven ideological frameworks -- is a recipe for a disaster.
KISS and YAGNI are examples of processes that require the person who is using them to actually think through the implications within the context of the problem being solved.
KISS and YAGNI did not cause the atrocities that you've seen. Those complex beasts of code existed because the team fundamentally didn't understand what they were doing.
It's like "agile" software development. Just declaring that you're following an agile methodology and scheduling stand-ups does not mean that you're going to be successful. It takes someone (and usually, many someones) who understand how to iteratively design and develop to be successful.
> It just takes a non-obvious path, and a few months of code and requirements evolution.
Can you expand on this comment? I'm interested in understanding what you mean by "non-obvious path".
Yep. That's the point, that's why I pointed it was blind.
Many people blindly follow and impose that kind of ideology. Most people that even mention YAGNI outside of a classroom are blind followers (for DRY things are more diverse).
By non-obvious path I mean things (requirement, underlining systems, user base size) changing in a way that was not unquestionably and blatantly obvious.
I can't agree with you more. Thank you for the follow-up.
Following KISS and YAGNI means you hard code lots of assumptions. When the requirements change, it means you have to fix all those subtle hard-coded assumptions everywhere and that is usually hard.
"The wrong abstraction is worse than no abstraction" comes to mind here. Very simple, repeated code that is unlikely to change (or unlikely to change everywhere at once because things are unrelated and just happen to do the same thing... -for now-) is much easier to read and maintain than short, clever, DRYed up code.
I'm big on linting code though... For me the more everyone's code looks the same the easier it is to read without doing mental gymnastics.
One of the reasons I enjoyed learning and writing Go was that it sort of encouraged keeping things "tight"
Often times programmers look at lines of code or number of classes written as a source of pride, when we really should be looking at these things as expenditures.
Slavish adherence to these principles (with the exception of DRY) violates one of my core principles: write the least code possible to solve a problem.
The single responsibility principle in particular isn't what I'd consider a fundamental principle of good code since it often can lead to writing an awful lot of unnecessary boilerplate. Short methods are similar (if a method is called only once I often consider that a code smell).
I think the culture of Java is also partly responsible for this: grand architectural designs are valued over terseness and simplicity. A system with stack traces as high as your arm are seen as something to be proud of.
For an instance of how it depends, see the classic Forth example for implementing a washing machine driver:
...
: RINSE FAUCETS OPEN TILL-FULL FAUCETS CLOSE ;
...
: WASHER WASH SPIN RINSE SPIN ;
Then a single call to WASHER is all that happens when the user presses the start button.Function calls should make things clearer, not less clear because of some other principle (like DRY or single responsibility or testability etc.). That's ultimately where I think the JavaLand culture has gone wrong, all these abstractions (many of them caused by being forced to live in a Kingdom of Nouns) that are sometimes quite useful in the large are always a pain in the small, but many times the small is all that is needed. Big projects are slowly learning they don't need to be so big, they can instead be a set of independent smaller projects, but it'll take more time.
Well I certainly disagree; it often makes sense to separate out a singly-called function if for no other reason than to be able to unit test that functionality individually.
The problem, of course, is that your critical thinking needs experience before you truly become capable of deciding if a particular principle should be followed or broken in a particular case.
In short, I think guidelines help, but you have to walk the path to know the path.
Anyway, it's all about UX. Certain types of programmers will want a certain types of interfaces. A well designed library will have been crafted to meet those programmers specific use cases.
I'd agree that finding the right level of abstraction is hard, easy to misjudge even for an experienced programmer with YAGNI in mind, and that you can do more damage by over-abstracting than not abstracting. Most programmers experience this the hard way sooner or later.
I'd disagree that having objects/functions do more than one thing or not be able to be pulled apart into individually testable units can be a good thing - except in very rare cases, or to compromise with existing legacy code.
Would you mind giving an example of where an object should do more than one thing?
Over-concern for extensibility is just a really big problem.
All the horrid Java libraries that we hated working with 10 years ago were created because of slavish devotion to the single responsibility principle, short methods, DRY, and testability. The absolute worst codebases I've ever seen sprung from an overaggressive concern for extensibility.
I do agree with the guiding principle, that when you're writing code, it should be written for other people to read and work with. But sometimes that actually does mean that a class should represent an object that does more than one thing, and you shouldn't fight it. Sometimes a gritty algorithm really does make the most sense if it's laid out as a 40 line function in one place rather than spread across 5 different classes so each part is independently swappable and testable. Sometimes you really don't need extensibility, you know that up-front, and building it in just pisses off everyone that ever has to touch your code because now they have to go through five or six "Find implementing classes" dances to find the actual code that runs when your interface methods (of which, of course, there's only a single implementation) are called. Don't even get me started on abuses of dependency injection when it's not necessary...
Part of me is glad that these concepts are so commonly discussed, because they really are good things to consider, and can make code much tidier and easier to work with in the best case. But it takes a lot of experience to know when and where you should and shouldn't follow these "rules", and it tends to be much easier to unwind a novice's spaghetti code and tidy it up than it is to pick apart a poorly conceived tangle of abstraction and put it back together in a way that makes sense.