I think people tend to err more on the “being clever side”. It just feels really cool, it tends to be really fun, it has kind of instant-gamification as soon as you measure it you have a “score”.
But in general if you need to be clever because performance doesn’t work out something you’ll know to make that choice.
For a lot of cases where performance would be nice I think it makes sense to not be super dumb about it and just in general have a plan and think about it when you get going. In in most usual cases just don’t do anything silly, like copying something all the time when you don’t have to. Or don’t pass something through a giant class/framework/library if you can do a three line for-loop or things like that. In some cases that can even make nicer, simpler, easier to understand code. Some time ago I had a funny discussion, because I refactored a bit of code making it clearer, easier, short and it as an unintended side effect went a lot faster. I was called out for “premature optimization”, when that wasn’t the goal. Even though it became soon after. The system was already struggling a bit.
So yes, there is places where you want to be smart, but there you know these are the rules of the exception. In many situations you don’t want to be and I’d also suggest being smart in design more than in code. And in general, if you know your code needs to be performant DO ACTUALLY MEASURE. I can’t count how often people ended up “optimizing” and thereby spending quite some time to produce hard to follow code, that actually wasn’t faster, or even smaller or never the bottleneck.
In my experience going for simplicity (less code, less huge libraries for tiny things) first is worthwhile and like I wrote, try to be smart on the design and not by using some language feature or trick that you just learned about, trying to apply it everywhere.
You will likely know when you actually write a high performance library, JIT compiler or low latency netcode. And even in these scenarios it’s important to be clever where it matters or clever can really quickly turn into… silly.
There are times to be clever, and times not to be.
Writing high-performance data structure libraries is a good time to be clever. Or a JIT compiler. Or low-latency netcode for an RTS game.
Yep, but it’s important that any such cleverness justifies its existence (to crib from the Onion) so that future maintainers understand why the code is the way that it is. Sometimes — like in a lot of games programming — that justification may be implicit in the domain. More often — like in most line-of-business applications — that justification probably needs to be more explicit: good comments describing the “why”, as well as sound and reproducible benchmarks demonstrating the “how”.
I wrote a short post last year about using a dash of “why” in names as a kind of warding against getting baited into mis-abstracting something by coincidental similarities.
This feels like less of a “don’t be clever” and “inheritance is garbage”. You try to build some sort of master interface that generalizes everything, and all someone has to do is inherit from it! But… things are different. And the properties of your system evolve over time. Suddenly your class hierarchy doesn’t make any sense.
Inheritance does this a lot. It allows for these seemingly infinite abstractions, so long as you can just view everything “as a” Thing. But then it never works out that way, or at least not for any project that evolves over time.
But I don’t see that as being more or less clever than a class constructor or any other solution. It’s actually a really simple thing. The mistake here was being clever, it was not knowing that there was another technique that would work even better.
TBH I think of things as either “I have to build it” or “I don’t have to build it” at this point. It’s not about one thing being clever or not - that’s failed me in terms of making the right tradeoffs. I just look at the problem, decide on the time I’m willing to invest, the work needed for each investment, and then I make the call. It’s not about “choose boring” or “innovation tokens” or “don’t be clever” or whatever, it’s about being an engineer and evaluating tradeoffs.
This feels like less of a “don’t be clever” and “inheritance is garbage”.
When I presented my last essay on how readability works, someone pointed out to me that inheritance tend to destroy code locality. In their experience the worst problem they have was having to jump from one file to another, and inheritance does this a lot.
I’ve had other arguments against inheritance (mostly the fact that the interface of a base class is a bit wider than its explicit API), but I haven’t thought about this back & forth (which matches my experience as well). Now I feel like I know why inheritance sucks so much.
Oh yes, it can be quite a pain having to jump across long chains of classes just to find out the behavior of the class you wanted to look at originally. Inheritance is fucking awful.
From the sound of it, this is just what Rails (and maybe other systems) calls “scaffolding” and is super simple. Just a template that emits the right class names.
In my early days I was super into getting rid of any and all boilerplate, but nowadays I think I have mostly seen through that. A little bit of boilerplate never hurts! It can actually make things more readable and inspectable in the long run, because which part of the code is being executed at a given point in time is clear and straightforward (as compared to using reflection, for example.)
It’s partly why I’m against using reflection and macros even when they’re available. They can reduce boilerplate, but most of the time simpler is better until it gets really unwieldy and you keep running into the same hard-to-catch bugs - at that point you know the cut points along which you can build a sensible abstraction (grug brained developer approach) but then you should really evaluate whether you need to pull out the big guns, or can solve your problem someway else.
Sufficient reflection and macros can turn one language into another language. If your first language has a strong macro system but a weak data model, for instance, then this can be a simple straight improvement. But you should definitely approach it with the trepidation and care of “I am creating a new language.”
I’m a heavy advocate of reflection and boilerplate-reduction at work - the trick is that the reflection library 1:1 models the business rules that we were using anyways. And of course, if something is wrong, there’s always backdoors.
I’ve done this at my current job. We had to move fast in order to survive and I built a One Abstraction To Rule Them AllTM, similarly for our CRUD controllers (well, more like django-admin screens, but same idea). It has also gotten unwieldy because of the numerous edge cases I had to support due to business requirements (mainly, I had to graft API endpoints on top of Django’s admin system because we had to implement Javascript and oh boy it sucks).
The main issue we had is that we had to work with django-admin’s existing assumptions. For instance, django-admin only accepts a well-known set of parameters on the “change list” page (object list page of your CRUD app). Adding any other parameters wipes them and replaces them with e=1, which is pretty annoying. Another big one is the fact that you relinquish control over your page layout when you let django-admin generate it for you. I worked around this by integrating django-crispy-forms to build the form layout in code but it’s very buggy due to it clashing with how django-admin thinks forms should work (it bypasses the AdminForm/AdminFieldset stuff entirely, which causes a lot of fun).
Another one of the major pain-points of django-admin is that it lives in a very HTML-templatey world, which makes adding things like computed/dependent fields in your forms (which needs JS) very painful. Currently what we do is instantiate the JS code by reading all of the formset data off the page (deserializing as necessary) and then shoving all of that into a MobX object so we can have some semblance of sanity, but the one page where we do this accounts for over 50% of the bugs within the system, it’s so very painful.
(One big thing I’m not getting into is Django forms. I can write a whole article about its major design problems, and probably will at some point.)
Having learned my lessons from all of this, I plan to write a new version of our admin dashboard system, this time focused on composing things together and not basing it on django-admin but rather just solving our problems ourselves. I’m also thinking of making the server a simple API which describes which pages are available and how pages should be laid out to the client (which itself is an SPA). I believe the old system is an experience we had to go through to know what we need for our real system.
Increasing maintenance burden isn’t always a bad tradeoff. There are times to be clever, and times not to be.
Writing high-performance data structure libraries is a good time to be clever. Or a JIT compiler. Or low-latency netcode for an RTS game.
I think people tend to err more on the “being clever side”. It just feels really cool, it tends to be really fun, it has kind of instant-gamification as soon as you measure it you have a “score”.
But in general if you need to be clever because performance doesn’t work out something you’ll know to make that choice.
For a lot of cases where performance would be nice I think it makes sense to not be super dumb about it and just in general have a plan and think about it when you get going. In in most usual cases just don’t do anything silly, like copying something all the time when you don’t have to. Or don’t pass something through a giant class/framework/library if you can do a three line for-loop or things like that. In some cases that can even make nicer, simpler, easier to understand code. Some time ago I had a funny discussion, because I refactored a bit of code making it clearer, easier, short and it as an unintended side effect went a lot faster. I was called out for “premature optimization”, when that wasn’t the goal. Even though it became soon after. The system was already struggling a bit.
So yes, there is places where you want to be smart, but there you know these are the rules of the exception. In many situations you don’t want to be and I’d also suggest being smart in design more than in code. And in general, if you know your code needs to be performant DO ACTUALLY MEASURE. I can’t count how often people ended up “optimizing” and thereby spending quite some time to produce hard to follow code, that actually wasn’t faster, or even smaller or never the bottleneck.
In my experience going for simplicity (less code, less huge libraries for tiny things) first is worthwhile and like I wrote, try to be smart on the design and not by using some language feature or trick that you just learned about, trying to apply it everywhere.
You will likely know when you actually write a high performance library, JIT compiler or low latency netcode. And even in these scenarios it’s important to be clever where it matters or clever can really quickly turn into… silly.
Yep, but it’s important that any such cleverness justifies its existence (to crib from the Onion) so that future maintainers understand why the code is the way that it is. Sometimes — like in a lot of games programming — that justification may be implicit in the domain. More often — like in most line-of-business applications — that justification probably needs to be more explicit: good comments describing the “why”, as well as sound and reproducible benchmarks demonstrating the “how”.
More like: Don’t try to cram superficially similar but essentially different things into one.
I wrote a short post last year about using a dash of “why” in names as a kind of warding against getting baited into mis-abstracting something by coincidental similarities.
(Easier said than done, of course…)
This feels like less of a “don’t be clever” and “inheritance is garbage”. You try to build some sort of master interface that generalizes everything, and all someone has to do is inherit from it! But… things are different. And the properties of your system evolve over time. Suddenly your class hierarchy doesn’t make any sense.
Inheritance does this a lot. It allows for these seemingly infinite abstractions, so long as you can just view everything “as a” Thing. But then it never works out that way, or at least not for any project that evolves over time.
But I don’t see that as being more or less clever than a class constructor or any other solution. It’s actually a really simple thing. The mistake here was being clever, it was not knowing that there was another technique that would work even better.
TBH I think of things as either “I have to build it” or “I don’t have to build it” at this point. It’s not about one thing being clever or not - that’s failed me in terms of making the right tradeoffs. I just look at the problem, decide on the time I’m willing to invest, the work needed for each investment, and then I make the call. It’s not about “choose boring” or “innovation tokens” or “don’t be clever” or whatever, it’s about being an engineer and evaluating tradeoffs.
When I presented my last essay on how readability works, someone pointed out to me that inheritance tend to destroy code locality. In their experience the worst problem they have was having to jump from one file to another, and inheritance does this a lot.
I’ve had other arguments against inheritance (mostly the fact that the interface of a base class is a bit wider than its explicit API), but I haven’t thought about this back & forth (which matches my experience as well). Now I feel like I know why inheritance sucks so much.
Oh yes, it can be quite a pain having to jump across long chains of classes just to find out the behavior of the class you wanted to look at originally. Inheritance is fucking awful.
Can’t edit but I meant “The mistake was not being clever”
This may be too clever as well!. Sometimes simply copy/pasting the code may be the best.
From the sound of it, this is just what Rails (and maybe other systems) calls “scaffolding” and is super simple. Just a template that emits the right class names.
Or you could just put the functionality in a library and use it whenever you need.
In my early days I was super into getting rid of any and all boilerplate, but nowadays I think I have mostly seen through that. A little bit of boilerplate never hurts! It can actually make things more readable and inspectable in the long run, because which part of the code is being executed at a given point in time is clear and straightforward (as compared to using reflection, for example.)
It’s partly why I’m against using reflection and macros even when they’re available. They can reduce boilerplate, but most of the time simpler is better until it gets really unwieldy and you keep running into the same hard-to-catch bugs - at that point you know the cut points along which you can build a sensible abstraction (grug brained developer approach) but then you should really evaluate whether you need to pull out the big guns, or can solve your problem someway else.
Sufficient reflection and macros can turn one language into another language. If your first language has a strong macro system but a weak data model, for instance, then this can be a simple straight improvement. But you should definitely approach it with the trepidation and care of “I am creating a new language.”
I’m a heavy advocate of reflection and boilerplate-reduction at work - the trick is that the reflection library 1:1 models the business rules that we were using anyways. And of course, if something is wrong, there’s always backdoors.
I really like Sandi Metz’s formulation of this advice: Duplication is far cheaper than the wrong abstraction
Classic “Inner Platform” fallacy from The Daily WTF.
https://thedailywtf.com/articles/The_Inner-Platform_Effect
I’ve done this at my current job. We had to move fast in order to survive and I built a One Abstraction To Rule Them AllTM, similarly for our CRUD controllers (well, more like django-admin screens, but same idea). It has also gotten unwieldy because of the numerous edge cases I had to support due to business requirements (mainly, I had to graft API endpoints on top of Django’s admin system because we had to implement Javascript and oh boy it sucks).
The main issue we had is that we had to work with django-admin’s existing assumptions. For instance, django-admin only accepts a well-known set of parameters on the “change list” page (object list page of your CRUD app). Adding any other parameters wipes them and replaces them with
e=1, which is pretty annoying. Another big one is the fact that you relinquish control over your page layout when you let django-admin generate it for you. I worked around this by integrating django-crispy-forms to build the form layout in code but it’s very buggy due to it clashing with how django-admin thinks forms should work (it bypasses the AdminForm/AdminFieldset stuff entirely, which causes a lot of fun).Another one of the major pain-points of django-admin is that it lives in a very HTML-templatey world, which makes adding things like computed/dependent fields in your forms (which needs JS) very painful. Currently what we do is instantiate the JS code by reading all of the formset data off the page (deserializing as necessary) and then shoving all of that into a MobX object so we can have some semblance of sanity, but the one page where we do this accounts for over 50% of the bugs within the system, it’s so very painful.
(One big thing I’m not getting into is Django forms. I can write a whole article about its major design problems, and probably will at some point.)
Having learned my lessons from all of this, I plan to write a new version of our admin dashboard system, this time focused on composing things together and not basing it on django-admin but rather just solving our problems ourselves. I’m also thinking of making the server a simple API which describes which pages are available and how pages should be laid out to the client (which itself is an SPA). I believe the old system is an experience we had to go through to know what we need for our real system.
[Comment removed by author]
[Comment removed by author]