Reading code is a skill (2020)
trishagee.com
trishagee.com
Reading and writing are two sides of the same coin. So why not test for the side that works much better for interview conditions and also allows more signal between a junior vs senior levels of experience?
Because you may not know the syntactic sugar syntaxes while being an expert at the language / field. They also may not know libraries that's been used by the code without researching beforehand.
OTOH, stripping the code to read from libraries and syntatic sugar syntaxes will reduce the code quality and sometimes making it harder to read / maintain. It doesn't reflect your tech stack in the company.
While writing code will guarantee that it's something the interviewee knows, with tolerable typo and syntaxes, and IMO it's more suitable for the interviewer to ask for written code than vice versa.
I'm not so sure about that. I can read assembly code but I can't write it at a competent level that an employer would require. Reading code is often easier than writing it.
Maybe an analogy is reading vs writing a movie script. A lot of us can read the actors lines and set directions and can then explain what the movie is about -- but most of us are not skillful enough to write a movie script.
The deciphering-vs-generation divide happens in human languages too. Many times, a person learning a foreign language can understand what someone is saying but if asked to generate a sentence to express a thought, they often will be flustered and halting in their speech as the brain struggles to find the next correct word to say.
The reading vs writing skills don't seem to progress equally.
I like the idea of code reviews in an interview though! Seeing how someone approaches a new code base would be a really pragmatic way to evaluate them. Having them make a targeted change to a smallish unfamiliar program would be informative as well.
If they're not able to critique the code and explain what it's doing, why it's changing, and what a better option might be they're going to struggle working in our environment where code reviews are emphasized as a huge part of the SDLC and not just the rubber stamp at the end.
I can read and understand many mathematical proofs and explain why it's true, but I could never have proven it myself. I can read and understand most of the great novels ever written, and even try to explain their greatness, but I could never have written any of them.
That being said, I agree that listening to someone explain and critique written code probably gives at least as good insight into their coding ability as having them whiteboard a leetcode problem.
Most code we're writing is more akin to seeing if you can read a newspaper.
He could see I understood how software worked, could reason about their style and approach, and I could fix the issue. I learned about their style and approach and got at least a feel for if their code base was going to be a nightmare or not.
With that said... it's hard to time your hiring interviews with when you have outstanding bug tickets that you can time box to an hour or two and don't require so much esoteric implementation details that it's a waste of time. Koans are GREAT but they do fall short on being a piece of the project that the applicant will be working on, so you miss out on seeing how the applicant reacts to your codebase and he misses out on sticking his toe in it.
It was fixing bugs and adding new features to existing code, usually stuff that was 5-10 years old at the least. If I didn't understand something I needed to wait for That Guy to show up. The one who did the first version some time in the last millennium.
Latest versions of anything was a pipe dream. Java version was at least 1-2 major versions behind the time. Business logic was PL/SQL and Perl scripts old enough to have a driver's license.
Also: print-debugging all the way. There were exactly zero ways to attach any kind of modern debugger to a 20 year old pile of code that had organically grown to what it was at the time.
I think I was 16-17 years into my career before I started a greenfield project from scratch, one I could decide the full tech stack from top to bottom.
Funny that you said that because i sometimes hear “store your database logic / invariants in the database!”
I guess it depends what kind of logic.
If the PL/SQL parser failed, there goes that message. Oh, well. Try again next day when the message arrives or dial the device and ask it to send it again.
I suggested after a while if we could store the raw message in a queue table and parse it from there. But no, everyone was too scared to change the system and we stuck to the old ways.
That's just laziness from the original developer. To me, no debugger support means it's time for a re-write.
when do you plan to get started on the linux kernel? that Linus is a lazy pri*k, he'll never get to it!
*printk
https://www.kernel.org/doc/html/latest/core-api/printk-basic...
(It took around 5 years and a team of 20 with millions of funding to "rewrite it" eventually and even they had to drop support for all but the latest devices. The old system is still chugging away, reading the old devices)
Also I would like to know how you would attach a debugger to a stack that contained, among others, ASM/C-code running on an embedded device on the other side of the world, some MFC/C code on a gateway windows computer, perl scripts running god knows where and a PL/SQL bundle on an Oracle server in another country and some Java logic on a different server.
This is the life legacy coders live.
Of course, if you have a modern stack you can just click "debug" on your IDE and it'll automatically attach to a remote process and let you step through the code. We don't have that :)
That sounds incredibly brittle. But each of these components should be debuggable individually and have a well defined interface.
A lot of shops are 9 versions behind right now.
One was very skilled at reading code. That skill didn't just help him with being able to understand easy to read code, but also to understand difficult to read code. This allowed him to go through the tech stack to find out what is wrong.
The other was very skilled at writing readable code, and more importantly, structuring its abstractions and interfaces just right. The primitives provided were exactly what he needed. Sometimes, when he wanted to expand the functionality, he finds out there were already primitives he can use for that expanded functionality.
It's not like you can't do both, but they are distinct skills and useful in different ways.
Code reviews can only enforce existing practices and style, rethinking architecture is a different matter.
This is just not the case in most software engineering jobs outside things like startups where you're on the original team, or your pet project.
In a team amongst other teams, you'll be reading code that you need to integrate with, reading colleagues' code when reviewing, and generally be producing much less than the sum of the code produced by everyone else on your team and your dependencies.
If you're the single owner of a component or subsystem, then it can be true, but that's not normally healthy for a software company - it implies a low bus factor.
Your typical company has a bus factor of 0, having one programmer who knows the code seems like a huge improvement over that.
I did find that I was able to use my code writing skills to bootstrap the code reading skills. If there was code that struck me as obtuse, I would try to refactor it. Sometimes I found that the code really could be written more simply. Othertimes I found that I had missed something important about what was going on. As I did this, I learned to better intuit what the original authors probably meant and where to look for confirmation/rebuttal.
For example, should a new grad be able to read your code without help? Should a 5 year old? At some point, you are spending more time writing design docs, refactoring the code, simplifying the tests, and gathering learning resources than you are just writing code that works.
Newer engineers rarely hear about how much they need to learn to read good code, and how much disagreement there is about what good code looks like. As a result it is easy to get into the mindset that "surprising" or "complex" code is bad. Instead, it would be a lot better if engineers are encouraged from the start to see reading code as a challenge. Nobody starts off knowing grep, folder organization conventions, go-to-definition shortcuts, and architectural design patterns needed to understand certain pieces of code.
To improve yourself, you are better off focusing on writing code, but at the organizational level, it's better for the team if people are willing to assume that reading code and writing readable code aren't an easy tasks.
I think this is probably more true for you than it is for us corporate schlubs :)
For those of us with less talent at writing a unique piece of code, the process of diving in and figuring out a mess is actually a learned skill that we get better at over time, possibly the most important one.
I've been in this industry for a couple of decades and I'd consider sacrificing a limb if the latter could somehow become easy.
1. I’ve had some luck using the “I have to clean the work site before I can assess the situation” analogy.
2. I’ve learned to refactor bad code as an effort to learning it, even if the refactor is not going to be pushed into the codebase. For me this is the best way, by far, to figure out messy code.
At this point I think it is a red flag, even if your manager DOES understand.
Because chances are THEIR manager won't understand. Or their manager's manager. Etc. You will look bad no matter what.
At this point I am beginning to think it is basically a no-win situation. No matter what.
Maybe that's why FAANG and top 5 capitalizations all have former engineers as CEO.
* If you have a working hypothesis as to what the code is doing, state that hypothesis as a comment.
* If you think you know what it is doing, making that comment executable. ie. put in an assert. Better an assert punches you in the face than you rely on an incorrect analysis.
* If you don't know WTF happening, or not sure, turn that comment into observability, add logging or something.
* Make microcommits using rebase or the like, add observability, add asserts, add unit tests, add refactorings, make behavioural changes.
* Do only one thing per commit, makes review and test much easier.
[1]: https://www.amazon.com/Code-Reading-Open-Source-Perspective/...
What are some techniques you've found help with reading code?
I'll start: breaking out trickier bits into isolated units and testing them to confirm my hypothesis. This gives me two pieces of information: it tells me what I do and don't understand about the unit I'm looking at, and it tells me what I don't yet know about the unit's dependencies (because to do a mechanical unit test, I need to mock / fake those deps, so now I know what assumptions I've made about those deps). This is useful for code with some very esoteric pieces in it (I spend a lot of time in graphics-land, where people do bad things to bit fields with equivalent meanings with the goal of saving a CPU cycle or two).
Bad code is each bad in its own way. There are just infinite combination of bad. Good luck on that. It is like telling kids what not to do. They still won't behave. It is like bring people with an "unfit culture" to your team. You might be able to work with them, you might be able to change them. But you also only got so much time
Recognizing this tradeoff explicitly might go a long way in improving how we approach the issue of training engineers. On a certain level, anyone motivated to do so can learn software engineering. But the amount of mentorship required differs significantly. And personally I'm not against doing that, but when organizations don't allocate time for intensive pairing/mentorship but expect senior engineers to somehow find time for it is what doesn't really work.
All organization has their own flavor of culture. Visionary vs mercenary, waterfall vs collaborative, explicit vs implicit. One can design/shape their organization to favor explicit knowledge sharing. I agree with you that it is a trade off. Just that all else being equal, I think writing good code offer way more leverage than getting better in reading code, knowing that they are not mutually exclusive
Yes. But only for the first couple of hours until someone who {missed the meeting, forgot something, didn't check, etc.} comes along with changes.
(Alas, this happened to me over the last two weeks and now the pristine simple logic is a fudged mess.)
A lot stuff is built without knowing what will come after, so often you will have layers upon layers that need to get dug through. Thats just how software will always be…
I took weeks or even months to understand how the Magento 2 (a php ecommerce framework) codebase works, probably one of the most complex codebases ive seen personally to date.
The fabled "lisp machines" of way back when could do similar. Most "image based" development environments let you jump to source of objects quite well. Play with the developer tools of your browser to get an idea of what used to be the goal for any dev environment.
And much of the modern set is heavily enabled by massive compute and memory. Such that often, it isn't that the checks you are talking about couldn't be done. They were often pushed to dedicated build sessions. Which often got skipped by developers.
I recall a project that was offshored where a local team was later asked to add functionality to it.
The code was so bad, it was useless to read it. The execs who decided to outsource in the first place did everything they could to get the local team to build on top of what they purchased. Finally, they brought in a very expensive consultant who told them flat out the only way he was going to touch it was a total rewrite. People were furious. In the end, he won and it ended up being a complete rewrite completed faster than the initial offshored project. For half the price.
I like that Golang started to undo some of this issue of hundreds of little files that jump all over.
C uses multiple files as well.
Yeah I'm trying to work on something for visual code navigation and also project management just a brain fart but yeah.
In any event, I don't know how language choice has much to do with this.
However, I find that writing reusable, highly modularized code sometimes comes at the expense of readability. In the olden days I wrote what I would call horizontally layered code with relatively clear path of execution but that leads to highly interdependent modules. Object oriented code or code that uses lots of dynamic resolution of call sites makes code reading much harder. Multi-threading, at least the traditional kind, adds more trouble to figure out what happens when just by looking at the code.
I think that's why patterns are important: if we give things names people broadly agree on it's much easier to piece together how different parts of the code should interact.
- Avoid deeply nested code; try to reduce "cyclomatic complexity". Refactor to use helper functions and guard statements as appropriate.
- Your code shouldn't need an IDE to be understood. Among other things, this often means avoiding overuse of type deduction features like auto in C++, var in Java, etc.
- A well-named function can be treated like a black box, without having to dive into its implementation to figure out what it's doing.
- Simple is better than clever. For instance, just because you see an opportunity to use the Y combinator (https://en.wikipedia.org/wiki/Fixed-point_combinator#Fixed-p...) or Duff's device doesn't mean that you should.
- Avoid premature optimization. Only reach for it if it's proven to be a bottleneck, preferably with real-world data (as microbenchmarks are not always representative).
- This one comes up most frequently in code review, and is probably the most important one: if your colleague doesn't understand your code, it might not be readable.
> Code that's hard to understand is often a result of an accumulation of things:
> The code was written at a time when the language/framework didn't do then what it does now;
> The code was written a while back and the fashions and "best practices" back then were different;
> Each line of code was written with readability in mind, but over time as more lines got added, the overall message was lost;
> People moved on and moved away, and now you haven't got anyone to ask about the business or technical reasons behind something (and of course the documentation is horribly out of date).
I would add:
> The code was written to meet a budget for time/latency/size and readability became secondary to meet those requirements. E.g. systems code/firmware/OS code often have a hard limit that they need to comply with. I've had to write code for an earlier job that had to fit within 16kb - compiled, and the code was anything but readable.
It's not like your colleagues set out to write bad code, or built systems that weren't extensible (at least, I hope that's the case). But they needed to get things done, and so they did what made sense at the time.
And over time, these organically-grown codebases develop more and more complexity and tend to retain historical baggage (often fueled by "if it ain't broke, don't fix it" coupled with reward structures typically not incentivizing the repaying of technical debt).
(not referring to your colleagues per se, but rather another general statement)
A popular fable is that of Chesterton's Fence. In short: if you don't understand why something is the way it is, then you should be wary of getting rid of it. This is incredibly applicable to refactors and clean rewrites.
Starting with a good foundation is crucial, since a lot of code is inevitably patterned on surrounding code. But making sure those incremental changes over time are also of high quality is important for making sure that the code quality doesn't degrade over time, because the new code of today is the existing code of tomorrow.
I sympathize with stringent product requirements -- that imposes tons of limitations on what you can reasonably do. But if you have nothing else, a comprehensive, well-structured, well-documented (and/or self-documenting) test suite can be an excellent way to document system behavior.
To me, it appears a meaningless phase, like saying "think harder". It's something said in a good faith effort to be helpful, but unfortunately isn't. I wonder if the "meaning" is intended to be something like, "learn to understand code"? Yet that replacement isn't any more helpful. Can code be understood broadly? I don't think to can be.
I had a boss once who tried to emphasize, "you have to learn to read code". Some of the things he wrote were comprehensible to me, some not. He was a senior and I was a junior. He also once said to me, "I don't get what's special about Lisp". When I tried to explain about macros and metaprogramming, he said "but Python has decorators". I assume my boss could "read code". He could certainly write it, he demonstrated an ability to explain the workings of a new codebase, and claimed he could "read code". Yet he appeared to be lost about how Lisp macro programming differed from Python decorators. I could have explained it better. Regardless, this tells me that "reading code" is non-transferrable. So, maybe "read code" is meant as a shorthand for "develop mastery within the language at hand so that you can quickly understand new code presented to you". Again, I'm failing to see how this is helpful, actionable advice. No matter which way I try to understand it, "leatn to read code" feels like it amounts to being told "be more experienced".
I find this particularly true in academic python codebases and C++ for some reason where single letter variables and cryptic contractions in identifiers is the norm. It's truly a shame that overly verbose java-isms spawned widespread disdain for descriptive variable names - I think one of the easiest low-hanging-fruit to most codebases is just using proper descriptive names for variables and functions, and other updating them accordingly when their meaning or purpose changes.
Yes, writing readable code is preferred, but as an individual developer, you can't just wait for others to make their code readable before you're able to understand and contribute to it, you need to work on your skill of reading code, even badly written code that is unreadable.
There's also another dimension I think people often overlook, don't just write readable code, write easily extendable, modifiable and testable code.
Having code that very clearly explains that it has a shared global that is manipulated and touched by each component doesn't really help with the task of adding or changing a feature without breaking anything else or taking forever to do so, because of having to touch too many things in the process.
And what's counter intuitive is that writing simple well designed code may actually make it harder to read, if you're not aware of the patterns or constructs that are leveraged to allow the code to be designed so it can be easily extended, modified or tested.
For example, you might find using Java streams to be harder to read than a for-loop. You might find dependency injection harder to follow than just creating new resources wherever they are needed. You might find using some DSL for templating harder to read than just doing your own string concatenation, etc. That is, until you learn about the patterns and constructs and become familiar with them, and good at reading and understanding them as well.
That's why I would say the most important things are in that order:
1. Get good at writing extendable, modifiable and testable code. That means, code that can be extended, modified and tested quickly with low risk of breaking other things and requiring minimal code changes in the smallest number of places.
2. Get good at reading code, both bad messy code with poor names, no consistency, and complicated designs and abstractions (this includes learning tricks to explore such code with logs, prints, debuggers, leveraging IDE features, etc); as well as good code that uses more advanced patterns and language features you don't yet understand very well.
3. Get good at making the code you write readable, that means clear intuitive names, useful comments, proper formatting, consistent patterns, not abstracting beyond what is relevant, stay close to the problem, etc.
I think that in order to get good at writing readable code, you need to spend a lot of time both reading code and watching other people read code you wrote. That's the way you learn which things make code hard to read or easy to read.
In fact, I'd go further and say you need to watch people in your target audience read code you wrote. Different subcultures find different styles easier or harder to read. When you're writing prose, you always have a target audience, and readability is relative to that audience. The same is true of code.
Just sharing because it felt relevant.
However, I am not as strong at getting a project going. I've underestimated how long it will take me to do something or missed key parts that should have been thought through. Hoping this chalks up to still being new-ish at my craft and in time I get better at this.
Is it me or is this some terrible advice? Most popular song lyrics could easily fit in a password word list.
a) coders who can read bad code and who also write bad code
b) coders who can't read bad code but who write good code
In that case I would always choose to work with person b.
tl;dr I don't think that they were disagreeing but adding on another important point