What Makes a Great Software Engineer? Just My Thoughts
elliot.land
elliot.land
1. Duplication is better than the wrong abstraction
2. Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.
3. Code must be written for people to read, and only incidentally for machines to execute
I think that sometimes people that 'more compact code' is what we mean be 'less code' and it's really not.
I think the differentiation is at the algorithmic level: it's not algorithm density, it's 'system clarity' that's key.
I find a nice rule of thumb is 'don't code two steps ahead'. Just because you may think you need to write a couple of 'duplicate lines' here and there, don't even bother abstracting for it. Wait until there's a few more.
Software people tend to outsmart themselves and build way ahead of what they need.
I think it's better to 'discover' the minutia of architecture than to plan, it all but the grandest elements.
An artists drawing a figure will 'sketch out the form' - that's the basic architecture you can do up front, but it lacks detail. The 'art' comes out incrementally piece by piece.
And the human aspect: it should hopefully read like English to a casual observer.
It also helps to think of it from a business perspective:
+ What the code does is an economic asset
+ But actual lines of code are a cost centre and an ongoing liability.
I'm much more aggressive about refactoring the former than the latter. When I was first starting off it was always hard for me to tell the difference so I abstracted a lot of things that I shouldn't have, and you could tell from the terrible method names that follow leaky abstractions.
I had to think about the first one for a moment, but I do agree. It's better to abstract later with greater knowledge than the much more difficult task of trying to debug or fix it later.
It's an outstanding list and great rules to work by!
This is a great idea! I'll try to remember this whenever I have the urge to lazily duplicate some code (which is quite often now that I think about it).
http://www.supercoders.com.au/blog/50characteristicsofagreat...
The problem is that people try to recruit for these things.
When recruiting, follows Joel's advice - smart and gets stuff done.
Student; is able to ask for help.
A lot of these are hard to objectively test for (in a recruiting sense) because they can be subjective or be dependant on the working environment itself. It never hurts to have more information that less, though!
Why? so difficult to change, poor documentation, no tests, etc.
A great software engineer, goes for simplicity but not in terms of laziness.
1. They keep the system simple enough and decoupled in the right way so that it's easy to change!
2. They have tests, not just unit tests, unit, functional, integration.
3. They document everything, code docs, design docs, design decisions docs, data structures docs, how to build, how to debug, how to maintain and change, etc
4. They do the really difficult stuff, they don't write the software for themselves, the develop for humans, customers. It's not about what you like, it's about what your customers like. This means having meeting and talking to others, getting feedbacks.
5. They keep learning, but they don't jump and adopt every fancy new technology, they stick to well proven stacks, so they tend to be boring.
An example of this is a strong senior engineer might take a lot of time laying the foundations of a project, the scaffolding needed to really make coding a breeze and enjoyable in the future. This is because they're lazy, they want the future to be easier so they invest today to make the future just a little bit easier.
That's because building something new is exiting and interesting, while doing something boring for 15 minutes is ... boring?
IMO you build the minimal thing that does the job, then after understanding the use case go back and refactor to deal with the accumulation of 15 minute pain. Doing it right at the beginning is the definition of premature optimization.
And on the topic of building new, this is a mistake many developers make: that it's easier to build new, then to refactor existing. What they fail to realize is that the old code they think is too difficult to maintain is battle tested, in production, has operational understanding associated with it. All software goes through this cycle, and so while it's fun to build something new, you are inviting debt while doing so b/c of the unknown production issues and edge cases that need to be figure out after it goes live. It's not always wrong to rewrite, but IMO, it's a last resort. BTW, this is why SOA is important: strong defined contracts/APIs between services allow for components to be replaced with a higher degree of confidence.
"I find that when someone's taking time to do something right in the present, they're a perfectionist with no ability to prioritize, whereas when someone took time to do something right in the past, they're a master artisan of great foresight."
I would also say that it's doubly important with the experience because I've seen people spend far too much time one something that they think will save them time in the future, but that future changes. It's a balancing act.
Let's see what impact "size of code" has on "time it takes to add a feature". Just consider a graph where the X axis is "size of code" and the Y axis is "time it takes to add a feature". Obviously, the function is increasing, and probably faster than linearly.
Let's see what impact "size of code" has on "number of bugs". Same thing.
Let's see what impact "size of code" has on "time it takes to find a bug". Same thing.
Let's see what impact "size of code" has on "time it takes to resolve a bug". Same thing.
Let's see what impact "size of code" has on "time it takes to refactor a piece of code". Same thing.
Let's see what impact "size of code" has on "time it takes to a newcomer to be productive on that codebase". Same thing.
And on and on and on.
There is two canonical points on those functions. The first canonical point is when the codebase is too big for the value it brings. When you are here, you are in deep trouble. Basically, the cost of doing anything in that codebase is too high in comparison of the value it brings. A sign you are here is when you spend more time in meetings discussing what to do, than time coding.
The second canonical point is when the codebase is notably small for the value it brings. When you are here, the project looks like magical. The programmers look like 10x programmers. Indeed, doing anything on the codebase costs surprisingly little in comparison of the value it brings. A sign you are here is when you are not asked for estimations.
A good developer would be able to write simple code for a given solution but a better developer would suggest a simpler solution leading to even simpler code.
You shouldn't expect that the client will know what they want even if they ask for it. There is however always an underlying reason why they think they need it. Take the time to understand that rather than just more billable hours. It will be better for you and them in the long run.
I always follow the KISS principle, if possible. It makes maintainability so much easier, not only in Software but also in Hardware. Every additional layer of complexity adds to the probability of failure.
Test suites should not just be thrown together, where every test is a "diff" of a giant log file! You do NOT want to be constantly fixing “tests” that “fail” due to trivial differences. Ensure that your code can completely expose all relevant state for one feature so that you can write precise checks and ignore any other output.
There should be no compile-time options (like #ifdef) that are not automatically built before every commit. Developers change things, and they have to know exactly what can break by doing so (especially with languages like C++, where a tiny change can cause true chaos). Put another way, don’t set yourself up to discover problems weeks from now at the worst possible moment; build defensive code and infrastructure.
Coincidentally, I had similar views in this piece some time back.. https://medium.com/@thallukrish/on-coding-e3c03771a369#.bb5k...
In these cases, we should strive to understand: * the complexity and the sources of it * how the complexity will change, and how much (as best we can, as predicting the future is hard) * what, if any, of that external complexity needs to be in the system we're building, and what can be pushed into human hands * how best to encapsulate that complexity so it doesn't spill over into parts of the system where it needn't be
It's all well and good to say "keep it simple", but that can be difficult when there are external forces that are saying "the system must do X when Y, A when B, and C when D, except when X. Oh, and if Y then C, except when A", etc,etc.
Example: Bob the sales manager doesn't want to view reports through your handy web portal. Bob wants them formatted in Excel sheets, zipped, and emailed to his corporate Exchange account daily. Bob is the only person in the whole company who doesn't like your portal, but he is powerful enough that his whims automatically become mission critical requirements. You either give Bob what he wants, messing up your clean design to implement functionality just for Bob, or you have failed to meet the business's needs and will lose the contract, end of story.
'Perspective' ...
Mini perspective: step away from the desk, do something literally creative. I play my guitar.
Major perspective: get the hell out of the city, actually do something on a weekend. Come back - all of a sudden you have fresh eyes and see things more clearly.
I actually think that soft devs should take more vacation.
You know that professional concert musicians only practice a few hours a day - any more is pointless. You just can't concentrate that long.
A really gutsy move would be for Google to give a bunch of 'long weekends' where nobody can work, or even more vacation. And see what the results are.
I find when my brain starts to grind to a halt I just go an do another un-related task to force myself to completely forget about the original task. It does help because when I go back to the original task my brain has to context-switch back into it and in doing so it may look at the problem a different way.
Extra holidays would be great! However, I think more holidays would make some engineers more productive and some less productive... I'm not sure if Google is still doing their 20% time, and if they are I know it doesn't apply to all the FTE engineers.
$this->executeAfter(5);
This is a short code, but it's not readable. What the hell is 5 here? Let's improve it a little bit. class Time {
protected $seconds;
private function __construct($seconds)
{
$this->seconds = $seconds;
}
public static function inSeconds($seconds)
{
return new self($seconds);
}
}
$this->executeAfter(Time::inSeconds(5));
I've just added ~10 lines of code, but the readability of executeAfter is way better. In addition, the executeAfter method is more flexible, since it can manipulate the Time object now.This is a very basic example to illustrate what I meant when drawing a line between the short code and readability.
private const TIME_DELAY_SECONDS = 5; $this->executeAfter(TIME_DELAY_IN_SECONDS);
# python
self.executeAfter(func=myCallback, delay_in_seconds=5)
# or
self.executeAfter(func=myCallback, after=delay(seconds=5)) $this->executeAfter(5); // Execute after 5 seconds
The above code is only 1 line, and is very clear. It does not add a brand new, unnecessary class, nor does it increase the surface area for bugs (adding a new class opens up the potential for new bugs, while adding a comment does not). public function executeAfter($seconds)
This code does not guarantee that the value of $seconds is correct, whereas this: public function executeAfter(Time $time)
...will help engineer to not make silly mistakes.It definitely depends on the language, but I personally like self-explanatory code and tend to think that inlined comments should only describe things that require explanation, such as complex logic.
$this->executeAfterSeconds(5);If you step back and understand that the problem is the method name is not clear, creating more code to get around that is totally unnecessary - just fix the method name. The other repliers hit the nail on the head with better options.
I personally would opt for the renaming of the method first:
$this->executeAfterSeconds(5);
If it was a third-party library or I was able to refactor the method itself I would go for constant named after what it represents, rather than what it is: $this->executeAfter(FIVE_SECONDS);
As a last resort (like if you had some reason why would couldn't physically change the source) a comment would also be totally acceptable.Even if you have experienced engineers doing the screening process (separate from the company being applied for) I still think it would be challenging because theres no "correct" way for a team to work and companies are so vastly different.
It definitely is something to think about. If you do some up with something, let me know :)
It's best to be be somewhere in between. It can only be learned by doing, not studying.
Now, if you define 'lazy' as someone who will go to great lenghts to not do boring tasks in the future, yes, I completly agree :)