4 ms·
All 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
by revvx 7y ago
All 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.
- Verdex 7y ago> 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. Yeah, there's competing forces involved. If you push too many things together that do not belong together, then you end up with code that is hard to comprehend. Additionally, if you spread too many things apart that should otherwise be together, then you end up with code that is hard to comprehend (your point). Finally, what things belong together and what things ought to be separated will depend on the domain and even the specific constraints within the domain. In order to objectively determine when things have gone poorly you have to factor in a lot of external details. For example, manual memory management is a detail that should almost always be someplace else because it isn't relevant to solving the problem at hand. So we did this with garbage collectors. However, sometimes we need this detail present (high performance computing and/or constrained hardware ie video games etc).
- revvx 7y ago> For example, manual memory management is a detail that should almost always be someplace else because it isn't relevant to solving the problem at hand. So we did this with garbage collectors. However, sometimes we need this detail present (high performance computing and/or constrained hardware ie video games etc). That's a great example. IMO looping is another case: I think it is clearer to use constructs like map/filter/group/sum/reduce instead of for/while/break/continue/etc. You only really need the procedural ones in hot-paths and such. Concurrency too: in Javascript you had to nest callbacks and promises, and they were also "too noisy". Async/await helps you keep your code more readable. I think Fibers in Ruby (and other languages) were a good idea too. - > If you push too many things together that do not belong together, then you end up with code that is hard to comprehend. That sums it up nicely. Cross-cutting concerns should be abstracted (but not hidden), as they don't belong together.