23 ms·
Clear is better than clever [pdf]
- coldtea 7y ago>To be clear, I don’t mean to dismiss the work of a lone programmer toiling on programs without anyone to pair with or learn from. I’ve been that person many times in my career as a programmer, it’s not fun. Wait, what? That's the most fun!
- arhyth 7y agojust new to Golang (in programming in general, actually) and this bug me. in the slide, it's indicated they are semantically the same. but how? (1) var thing Thing json.Unmarshall(reader, &thing) (2) var thing *Thing = new(Thing) json.Unmarshall(reader, thing) in (1) thing is a variable for a value of type Thing, while in (2) thing is a variable for a Thing pointer shouldn't thing (2) need to be dereferenced first?
- eraxley 7y agoYou are right that (2) needs to be dereferenced first, however, in go a pointer is autmatically dereferenced when accessing its properties or "methods". What I mean by this is that if you want to access a property on thing, it would look the same. In C++ it would look like this: (1) x = thing.Property; (2) x = thing->Property; (or (*thing).Property) but in go, both would look the same, like this: (1) x = thing.Property (2) x = thing.Property So there is a difference syntactically, and the compiler will handle them differently, but whether the difference is semantically relevant is debatable.
- derangedHorse 7y agoThat's one of the things that I hate about Go. That and how they force me to write an 'else' on the same line as the closing brace of the 'if'.
- saagarjha 7y agoRust does this too, I think.
- steveklabnik 7y ago(Yes, it does.)
- falsedan 7y agojson.Unmarshall takes a pointer as its second parameter. In (1), a pointer to thing is created using &. In (2), thing is already a pointer and can be passed as-is. They're semantically the same. The type of thing isn't, but that's syntax.
- ttctciyf 7y agoWell, the second arg of Unmarshall is expected to be some kind of pointer. > Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v. If v is nil or not a pointer, Unmarshal returns an InvalidUnmarshalError.[1] In (1) the '&' in '&thing' means roughly "the address of", so you are passing "the address of thing" i.e. a Thing pointer that points to the thing variable. 1: https://golang.org/src/encoding/json/decode.go https://golang.org/src/encoding/json/decode.go
- jodrellblank 7y agoPrograms must be written for people to read, and only incidentally for machines to execute. –Hal Abelson and Gerald Sussman Often quoted, and I wonder if there's much evidence supporting it? Let's replace "machines execute it" with "people use it without reading the source code", and we can say that code is run many many more times than it is read. Focusing on readability of the code and putting "machine execution" second, means putting the user experience second, after the developer experience. Imagine a race car engine which is designed first to be simple for an unfamiliar contract mechanic to repair, and secondly to power a race car, with the justification that the engine "will be repaired more often than it will be built". I have my suspicions that software which is fast, responsive, and a pleasure to use is designed by people who put the machine first; even if that means using difficult languages and low levels of abstraction and requires high levels of skill. The most important skill for a programmer is the ability to effectively communicate ideas Iverson Notation was designed as an improved way for people to communicate ideas, years before it got turned into the programming language APL. Look where that got it. Go programmers realise that code is written to be read and so place the act of reading code above the act of writing it. Go goes so far as to enforce, via tooling and custom, that all code be formatted in a specific style. Books are created to be read, not written. That doesn't happen by enforcing that all books have the same style and formatting. If software cannot be maintained, then it will be rewritten; and that could be the last time your company will invest in Go. Pushing a cult is the last word in the slides?
- zaphar 7y agoFocusing on readability of the code and putting "machine execution" second, means putting the user experience second Nothing kills a users experience like buggy code. Nothing contributes more to buggy code than unreadable or unclear code. This is why another proverb often quoted is "First make it correct, then make it fast". Fast but buggy code will lose to slower but correct code everytime.
- titzer 7y ago> Fast but buggy code will lose to slower but correct code everytime. I wish this were true, but unfortunately C++ proves this is not the case. Fast and buggy seems to be the holy grail there.
- userbinator 7y agoIt's interesting that the comparator function is one of the examples, because that's something which shows how people often confuse verbose with clear; I think it's best written in a single line: return a < b ? -1 : a > b ? 1 : 0; That's one line, compared with the 10(!) of his proposed method using a switch statement. Having worked with some "modern" codebases, I think verbosity is a bigger problem that's become especially prevalent recently, since the idea of making a simple thing simple is so enticing and easy, but emphasising "micro-readability" to that extent (that's 10 lines of code for two simple decisions) will bloat the code and make "macro-readability" harder, and it's the latter that really matters for the understanding of complex software. Anyone who has the experience of reading lots of highly trivial functions like those, and gotten the feeling "I've read a lot of code, and I understand what each little bit does, but what is the whole thing trying to do?" That's a symptom of excessive verbosity. To offer another (subjective) point, I think the lack of semicolons or other statement delimiters does make code harder to read, as it makes it look similar to a run-on sentence. Natural languages have punctuation for a similar reason --- you can quickly scan over sentences by finding their delimiters. Also, the counterargument: https://www.linusakesson.net/programming/kernighans-lever/index.php https://www.linusakesson.net/programming/kernighans-lever/in...
- __s 7y agoMeanwhile I'll write (a>b)-(a<b), which is what Python suggests after having removed cmp
- userbinator 7y agoThat works nicely too, for those languages with "1-0" booleans. For languages without them (Java, C# come to mind), the ternary is the next-best way.
- alexgmcm 7y agoI mainly use Python but that would still be unclear to me. I think the ternary operator is the sweet spot between too verbose and too confusing.
- eternalban 7y ago"When declaring and initializing do ...". Thank you Dave but some of us prefer explicit 'vars' all the time and reserve := for quickie peripheral variables.
- humblebee 7y agoDo you have some examples of this style in go? This was once a gripe of mine with go, but I have since just gotten use to the lack of clarity around types I'm working with. I came from JavaScript so it wasn't too hard, but I was writing mostly with C when I first encounter go. With JavaScript I heavily leaned on properly named variables to understand the underlying type within the context of the code I was working on. In go however, short and nondestructive variables are common place, with the lack of types in declarations I have to reply on properly named functions and general context of the code I'm working in. I'd be curious to see what go looks like with explicit vars for most things.
- scraegg 7y agoTo a certain degree. I'd say it like this: Too clear is not clear at all. Too clever is not clever anymore.
- olah_1 7y agoSometimes clever means succinct. And maintaining less lines of code can also lead to less bugs.
- throwaway55554 7y agoSometimes. But only sometimes. A clever one liner that is easy to misunderstand when reading it while trying to find a production issue with eyes on you is NOT fun at all. And it isn't worth the two lines it saved. Less code does not mean less bugs.
- dboreham 7y agoFewer
- fauigerzigerk 7y agoSo you're hoping that bugs are distinct and countable ... :)
- Consultant32452 7y agoI'm not disagreeing with you, and maybe this is just my personal experience, but there seems to be a movement that promotes succinctness over all other qualifications. It's as if someone's comp-sci class had a competition for who could accomplish some task with the fewest bytes of code and a whole generation of programmers never got out of that mode. I've heard comedians talk about a set not having any fat in it... every phrase, every short pause, etc. is exactly what is needed to make the bit funny. To stretch this analogy a bit, you can trim so much fat that you start taking the meat and the audience can no longer follow the joke. We need that kind of philosophy in coding.
- olah_1 7y agoOn the contrary, I think the zeitgeist for the last 5-10ish years has been the opposite. "Verbose and explicit > Succinct and implicit". I think python and golang are largely responsible for this kind of movement in recent years. Because of that, I find languages like Perl 6 to be a breath of fresh air.
- sly010 7y agoFrom the second slide: "To be clear, when I say I, I don’t mean me, I mean you. And when I say, your code, I also mean you, but in the third person."
- xixixao 7y agoNice intro, which then jumps to an absolutely arbitrary (for someone who hasn't coded in Go) set of recommendations, which are (imo) poorly argued from the perspective of clarity. Especially the case of "var person int", how is it cleaner than "var person int = 0" - and why are we even talking about zero values when the variable was not supposed to be intialized. Many decent imperative languages let you not intiialize a variable, and then not allow you to reference it until it's been initialized in all branches. Mant things are left on the table. First of all, the audience. If you're explaining something to someone, you must consider the audience to be successful. So are we writing code to be read by Go experts? Go noobs? Software Engineers with general background in many lanaguages, but perhaps not go? Noob programmers? You want to assume the lowest level of understanding that is imaginable, and that's probably the last but one I listed in corporate environment. Now that you have your audience, you gotta wrestle with some high level issues that then trickle down to concrete code. Explicit over implicit. Purity over mutable state. Small code units over monolithic code units. DRY. Verbose naming (that doesn't violate DRY). You gotta agree on the right level of code documentation - especially important for anything that resembles library code (or is an "abstraction"). The hard parts (like good naming) are not Lintable (of course Lint anything you can, use auto-formatter etc.), they are cultured through countless interactions between people in your organization. Getting third opinions is a valuable method here.
- jerf 7y ago'Especially the case of "var person int", how is it cleaner than "var person int = 0"' Because they're equivalent. There no uninitialized values in Go; everything not given an explicit initializer is zeroed out. "So are we writing code to be read by Go experts? Go noobs? Software Engineers with general background in many lanaguages, but perhaps not go? Noob programmers?" One of the nice things about Go not particularly appreciated by a lot of the HN community (as evidenced by a lot of the other comment threads) is precisely that code targeted to those various targets will not differ that much. I also program in Perl 5 a lot, unfortunately, and to make it professionally palatable, I do indeed have to program in a dialect of Perl that has been chosen to be something that can be read even if you are not a Perl expert. I tend to avoid dragging in huge rewrites of the object system, for instance, in favor of the built-in support, which I can reasonably expect everyone to know. I avoid using autovivification, etc. I often see code written by a Perl expert in the code base, and have at times even torn it apart and put it back in the LCD dialect we locally use, just so we don't have this sudden, confusing chunk of code. The code is messy for many of the other reasons I've come to dislike large dynamic-language codebases, but you won't encounter $% += $(-/\#/[[]]/l; in our code base. In Go, there's not much need to cut back to a subset of the language. There's only a handful of constructs you need to avoid, and it's the same code for almost every level. The "programmer who only knows half of Go" isn't that big a deal, because anyone who knows half of Go can finish the rest of it in a couple of hours. I value this professionally. I understand why people who seek other things from their programming do not, because I also have times I seek those things. My personal codebase is much more mixed. But when I'm being paid to produce code, I'm not being paid to feel like I'm really clever for stringing one line together that downloads a web page, parses it through an HTML parser, and strips out all the text, returning the text nodes as a stream of strings or a concatenated string or whatever. Yes, I can do that, in several languages, but except in certain specialized circumstances, that is low quality professional code, even if it "works", because when I expect someone with two years of experience to make a change to that code base, I'd like them to be able to do it in a reasonable period of time, and with a reasonable understanding of the consequences and tradeoffs, not as a modification of an incantation. (Every time someone tells some story about the one guy who has been around a long time and knows all the things and is the only one who can make any changes to any production system because it's all his code and only he can understand it because it's all crazy nonsense to anyone else, and all the schedules are busted because even he no longer can keep up with the mess, we all have a good laugh and condemn him. But when we get told that in order to avoid that, we may have to not write the cleverest possible code that we can, and that we need to prefer simple, effective code that works, and is easy to understand, a lot of us get all offended at the infringement of our rights to cleverness. Well... it's the same thing, just two different angles.)
- qgadrian 7y agoThe message is clear, and clever
- auggierose 7y ago> To be clear, I don’t mean to dismiss the work of a lone programmer toiling on programs without anyone to pair with or learn from. I’ve been that person many times in my career as a programmer, it’s not fun. I'd say it's a lot of fun.
- rohan1024 7y agoI agree with the author maybe because for first four years of my software development career I was a lone developer. Maybe Dave was in similar situation.
- ahartmetz 7y agoIt completely depends on the quality of your collaborators. Are they good developers, do they have somewhat compatible views, and are they reasonably friendly? I prefer working in a highly capable group.
- zeroxfe 7y agoYeah, working with other good devs is fun too, but working alone is a special kind of fun. :-)
- deleted 7y ago[deleted]
- erikpukinskis 7y agoIf you have collaborators you are not in that situation.
- r_singh 7y agoI agree with this sentiment, however, it does not continue to remain fun if that's the only development experience one has
- designcode 7y agoI’d say it’s in fact where the real fun happens
- del_operator 7y agoReminds me of an early code review that preferred applying De Morgan’s law and being initially surprised. It was a small chat that started talks of the clever vs readability on a small team. Similar things would happen with say map(f) |> map(g) vs map(f |> g). My clev-dar just reminds me to ping the reviewer or leave a note.
- revvx 7y agoYeah. I always see the De Morgan's Law being used to make the code look prettier or look less complicated. I'd say it's objectively prettier most of the time, but unfortunately that's exactly what introduces double negatives or other ambiguous stuff and makes the code more confusing. It's a nice refactoring step, though: apply it, extract the condition into a well-named variable and test that condition.
- lopatin 7y agoI love Go and respect the philosophy of the language, but I do agree with userbinator that the question of how to achieve that becomes much more interesting and less bike-sheddy when talking about macro readability. I’d also like to point out the irony of how an article about readability is borderline unreadable on a mobile device. The font size is microscopic on page load, and after zooming in to normal font size on a standard iPhone screen, I can only fit 1/3 of the width of the text without scrolling horizontally. If your content is in paragraph form, please just use html.
- MichailP 7y agoThis reminds of a quote by Nikola Tesla: “The scientists of today think deeply instead of clearly. One must be sane to think clearly, but one can think deeply and be quite insane.” I always preferred clarity over "cleverness" not just in programming, but also when say reading a book on a technical subject. Clarity evokes feeling of beauty I would dare say.
- hu3 7y ago“Simplicity is a great virtue but it requires hard work to achieve it and education to appreciate it. And to make matters worse: complexity sells better.” - Dijkstra
- irrational 7y agoThis is my mantra for JavaScript. So much of the post ES6 code I see seems more clever than clear.
- diehunde 7y agoAlso happens a lot with scripting languages such as Ruby or Python. It's like people are looking for cool ways of writing less lines of code.
- commandlinefan 7y agoI have noticed that ES6+ is a lot more functional, so there’s a lot more map/reduce stuff in modern Javascript. Although it’s definitely more succinct than the equivalent for loop implementations, I often find myself having to copy and paste it somewhere else and run it in stages to see what it’s doing whereas with the for loop variation, I could just step through the actual code as is with a debugger to make sense of it.
- gautam884095 7y agoyes you are absolutely right
- buu700 7y agoI agree with the sentiment, but not with Go's interpretation. To me, higher-order functions like map and reduce are much easier to read than the equivalent for loops and are less likely to have subtle bugs (off-by-one errors, etc.) because they more directly capture the author's intent.
- revvx 7y agoI agree 100%. In my experience, loops and recursion are lower level constructs that should ideally be used only in libraries (map, filter, limit, sum, distinct, etc [1]) and in hot code paths (for optimization). Kevlin Henney makes a good observation on one of his talks about how "you have written all the loops you will never need in your life" [2], and those are constructs such as map/filterand friends, present in LINQ, Java Streams, Ruby Enumerables, etc. His argument is that code made with those primitive is clearer than code made than procedural loops, because when you read it you don't have to "execute the code in your head". I'd argue that using procedural loops is what's actually "too clever" here, despite being more verbose! - [1] I have mixed feelings about "Reduce". It is hard to use and, most of the time, hard to read as well. It's too low-level. [2] https://youtu.be/APUCMSPiNh4?t=3317 https://youtu.be/APUCMSPiNh4?t=3317
- buu700 7y agoI have mixed feelings about "Reduce". It is hard to use and, most of the time, hard to read as well. It's too low-level. Fair enough. I'd certainly always prefer a special case like `.sum()` over something like `.reduce((a, b) => a + b, 0)` where applicable. I don't think reduce is overly complicated, but it's not much of a step up over a for loop and can be abused to write code that's more confusing to reason about than an equivalent loop.
- Verdex 7y agoI'm fairly sure that we as a community can come up with an objective measure for the phrase "clever code". Something like: Exploits implicit and/or hidden details to correctly function. However, I've never seen a convincing and objective description of clear code. Sometimes people mean that clear code is verbose code. Sometimes people mean that clear code is uniformly formatted code. [For best results we should manually format the code the same. This builds character. Automatically formatting will not be considered.] Similarly, sometimes people mean code smells are unfamiliar code. Or that code smells are patterns that they misused once and it bit them. Objective methods of describing the ability for people to comprehend code when all else is equal do not exist as far as I have been able to see. The best we have is cyclomatic complexity, but there's some reason to believe that line count may be a better indicator (which can't be good). And cyclomatic complexity completely misses the effect of mutable or immutable state, the presence of bad APIs, poor variable naming, etc.
- revvx 7y agoAll IMO: > However, I've never seen a convincing and objective description of clear code. I take "clever code" to mean "code that looks pretty on surface the but requires the reader to dig in in order to understand the intent". The problem with clever code is that it is misleading. It lacks empathy. You assume the next programmer will understand it at first glance just because it looks cool or pretty, but they'll struggle to parse it. --- > Sometimes people mean that clear code is verbose code. I also consider some verbose code to be "clever code" too. Most of the time it is just people using hammers where screwdrivers would be more appropriate: - Unnecessary structures - eg: classes that could be functions taking 2x or 3x more space - Unnecessary usage of polymorphism - eg: inheritance chain that could be a simple if - Excessive indirection - eg: layered code where most of the time the layers don't do anything - Plain wrong abstractions - eg: using query builders to build queries that would be smaller and more readable in SQL - Fear of making classes too big - eg: instead of adding a method to a class, making a second class that knows too much about the innards of the first one - Procedural code disguised as OOP - eg: breaking up a method into multiple private ones, but ending up having a lot of instance variables that were local variables before. When everything could be a single function. --- > The best we have is cyclomatic complexity, but there's some reason to believe that line count may be a better indicator (which can't be good). And cyclomatic complexity completely misses the effect of mutable or immutable state, the presence of bad APIs, poor variable naming, etc. Great observations. I agree 100%. Since you mentioned it, I find cyclomatic complexity a bit too easy to game. I always wanted to have a metric that prevented people doing that, and took multiple methods into account. When you break a method in three or four without adding REAL abstractions (a.k.a. "things you don't have to follow with the debugger to understand"), you're not making it easier to read, in fact you're making it harder because the reader has to jump around your code. Of course, it's harder for machines to know the difference between good and bad abstractions. But I think we should take that into account in code reviews and such.
- 29athrowaway 7y agoAmbiguous proverbs.
- piinbinary 7y agoWhen you are talking about making code "clear," I think it's important to ask "clear about what?" Is it: * Clear about what the machine is doing? * Clear about what the possible error cases are? * Clear about the purpose of the code? Go often prioritizes making it clear what the machine is doing over making the purpose of the code clear. In some languages you might write `newList = map(myFunc, myList)`. This is clear about what I intend, but not how it is accomplished[0]. The equivalent for-range loop in Go is clear about what it does at the expense of diluting the intent. [0] for example, that map function could apply to the first element first, or the last element first, or it could apply to all the elements in parallel.
- kazinator 7y agoUsing the keyword switch for successive condition testing is a travesty. Switching means taking one of several code paths based on a value. The problem with using a switch syntactic sugar for conditional testing is that the cases are not exclusive: switch { a > 5: ... a > 3: ... } Here a > 3 is not reachable: it tests a condition that overlaps with a > 5 completely, and a > 5 is earlier. This form of switch would be useful (and actually live up to the name) if the compiler were required to diagnose against overlapping cases, and also incomplete cases. switch { a < 3: ... a > 5: ... error: domain of variable "a" not covered in switch without a default: clause } switch { a > 10: ... a > 5: ... error: overlapping cases in switch }
- 0xffff2 7y agoEither my minimal understanding of Go is showing, or this just proves your point, but don't you have your conditions backwards? `a == 4` would fail the `a > 5` condition and so execute the `a > 3` case, right?
- kazinator 7y agoYes, I should have a > 3 first, since I was trying to say that the values of a that would trigger a > 5 are caught by a > 3. If a clause is obviously unreachable, that's likely a bug. But it doesn't mean that reversing the clauses is the right fix, either. They continue to overlap; and if that is allowed, this is not a "switch" in any sense.
- dwighttk 7y agoIs this a scientology ad? (didn't want to download a pdf)
- picacho 7y agokinda funny... the talk is precise, touche, and cliche. Like software engineering 101 from top-notched software companies. Especially from those top-notched software companies which writing C++ code. That is, if C++ were not coded in this way mentioned in this talk, the project is just doomed to fail. But for Go Lang, even you don't coded in these mind sets, the project still works, and maintainable, just that, it's shit code.
- atoav 7y agoClear is indeed better than clever, but clear and clever are not mutually exclusive poles. You can be clear and stupid, clever and stupid clever and clear, all is possible.
- deleted 7y ago[deleted]
- xyproto 7y agoThere is a mistake in the presentation. This compiles, even without a return after the if/else block. func comp(a, b int) int { if a < b { return -1 } else if a > b { return 1 } else { return 0 } }