Comment by DamonHD
2 days ago
The best code, eg for embedded systems, is as simple as it can possibly be, to be maintainable and eg to let the compiler optimise it well, possibly across multiple targets. Sometimes very clever is needed, but the scope of that cleverness should always be minimised and weighed against the downsides.
Let me tell you about a key method in the root pricing class for the derivs/credit desk of a major international bank that was all very clever ... and wrong ... as was its sole comment ... and not entirely coincidentally that desk has gone and its host brand also...
Simple code means just doing the thing. It's often misinterpreted to mean code made of lots of small pieces (spaghetti with meatballs code) but this is simply not the case. Often, avoiding abstractions leads to simpler code.
At my job we're disqualifying candidates who don't use enough unnecessary classes. I didn't use them, but they proceeded with my interview because I happened to use some other tricks that showed good knowledge of C++. I think the candidate who just wrote the code to solve the task was the best solution, but I'm not in charge of hiring.
Without revealing the actual interview task, let's pretend it was to write a program that lowpass filters a .wav file. The answer we're apparently looking for is to read the input into a vector, FFT it, zero out the second half, unFFT it, and write the output file. And you must have a class called FFT, one called File, FrequencyDomainFile, and InverseFFT. Because that's simple logical organization of code, right? Meanwhile, the actual simple way to do it is to open the input and output files, copy the header, and proceed through the file one sample at a time doing a convolution on a ring buffer. This latter way involves less code, less computation, less memory, and is all-around better. If you think the ring buffer is too risky, you can still do a convolution over the whole file loaded into memory, and still come out ahead of the FFT solution.
But if you do it this way, we think you didn't use enough abstraction so we reject you. Which is insane. Some time after I got this job, I found out I would have also been rejected if not for a few thoughtful comments, which were apparently some of the very few signals that "this guy knows what he's doing and has chosen not to write classes" rather than "this guy doesn't know how classes work."
> Often, avoiding abstractions leads to simpler code.... But if you do it this way, we think you didn't use enough abstraction so we reject you.
I think you've unwittingly bought into your hiring team's fallacy that classes are somehow essential to "abstraction". They are not. Wikipedia:
> Abstraction is the process of generalizing rules and concepts from specific examples, literal (real or concrete) signifiers, first principles, or other methods. The result of the process, an abstraction, is a concept that acts as a common noun for all subordinate concepts and connects any related concepts as a group, field, or category.[1]
The fundamental abstraction in computer programs is the function. A class is principally a means of combination that sometimes incidentally creates a useful (but relatively complex) abstraction, by modeling some domain object. But the most natural expression of a "generalized rule" is of course the thing that takes some inputs and directly computes an output from them.
Of course, we also abstract when we assign semantics to some part of the program state, for example by using an enumeration rather than an integer. But in that case we are doing it in reverse; we have already noticed that the cases can be generalized as integers, and then explicitly... enumerate what it is that we're generalizing.
(The reason that "FFT" etc. classes are so grating is that the process of that computation hardly makes sense to model; the input and output do, but both of these are just semantic interpretations of a sequence of values. You could staple a runtime "time-domain" or "frequency-domain" type to those sequences; but the pipeline is so simple that there is never a real opportunity for confusion, nor reason for runtime introspection. I almost wonder if the hiring team comes from a Java background, where classes are required to hold the code?)
If I were writing the convolution, it would still probably involve quite a few functions, because I like to make my functions as short as feasible, hewing closely to SRP. Perhaps the ring buffer would be a class — because that would allow a good way to separate the logic of accessing the underlying array slots that make the ring buffer work, from the logic of actually using the ring buffer to do the convolution.
(I'm not sure offhand what you'd need to convolve with to get the same result as "zeroing out the second half" of the FFT. I guess a sinc pulse? But the simple convolutions I'd think of doing to implement "low-pass filter" would certainly have a different frequency characteristic.)
Well, I substituted the task for a different but related one, so the substitute task is not fully specified in detail and perfectly mathematically correct - just good enough to show the principle.
We have given extra points to a candidate for having an FFT class even though it should obviously be a function. And the comments clearly indicated that candidate simply thought everything should be a class and was skeptical of things not being classes.