Tuesday, October 30, 2007

Programmers Anonymous: Confessions of a Terrible Software Developer

Marc has posted his "Confessions of a Terrible Programmer." The overriding tone of the post can be summed up in the following pseudo-Zen quotes:

You will never become a Great Programmer until you acknowledge that you will always be a Terrible Programmer.


You will remain a Great Programmer for only as long as you acknowledge that you are still a Terrible Programmer.

Marc does a very good job of stating how he overcomes his "terribleness" to provide working software. According to Marc, his solutions are doing a good job of hiding the fact that he is a terrible developer. However, I believe that agile practices present different solutions to these problems. In broad terms, Marc favors failing fast where I favor multiple levels of testing, Test-Driven Design, and the fast feedback loops provided by good test coverage coupled with Continuous Integration.

To address more specifics, I have provided a summary of Marc's solutions along with where I think agile solves these in a different way.

  1. Marc says he favors strong typing to prevent problems. Having done most of my work in static languages (Java, C#), and some in dynamic language (Groovy), at this point I prefer solid unit test coverage (100% with excuses). Test-first design helps with this too. Once I have good test coverage, those tests are unearthing the same problems that the compiler would. With the dynamic languages, I find the same errors a bit later, but I get the benefit of code that I find to be much easier to read.

  2. Marc favors programming assertions. Marc is a paranoid programmer. He will assert that something is not null even when he controls both sides of the interface - just in case he might change something later. I personally find that assertions and paranoid programming in general fall under the related headings of YAGNI and nosiy code. Instead, I prefer solid unit testing and a tester that knows how to unearth the edge cases. Write unit tests that assert that the service in question does not return null to box in the behavior.

  3. Marc says he will, "ruthlessly try to break [his] own code." It appears that Marc is trying to accomplish through developer testing what should be done by an actual tester. While I agree that developers should be generating tests that give great code coverage, it is a waste of time to make them switch hats and become a tester for their own code. Hire a tester. They think differently from developers. Their concerns are different.

  4. Marc favors code reviews. I favor pair programming. Both provide feedback, but I want my feedback while I'm "in the zone." I want my feedback immediately. I don't want you to sit back and wait for me to find my own bugs. If you see something, tell me. Tell me as soon as it looks like I've finished typing or as soon as it looks like I'm looking for the bug. This way, I can fix the problem without having to make the context switch to come back to it later. Plus, the quality of the review is better since the other developer should be equally engaged in the generation of the code while it is being generated.

To be honest, our team is not able to follow all of these guidelines at the moment. Our biggest problem is not having a dedicated software tester embedded with the team. We ARE having to generate the types of tests that a dedicated tester should be doing. And, we are missing some things that a tester would catch much earlier in the development process. I feel that this missing component of our team IS hurting our velocity.

Am I a terrible programmer? Yes. If you find me stating otherwise, please redirect me to some of my own code - something that I wrote yesterday should do just fine.

What are you doing to hide the fact that you're a terrible developer? I would love to hear.


terry said...

I think if you're dealing with a strongly typed language, you need to keep as much as possible strongly typed. This isn't really possible in Java (the parameterized types aren't really strongly typed), but at least the compiler catches a lot of it. It seems to be really necessary when you've got 123871239812 levels of code and need to find out if you Fd up asap. If you can keep things smaller, the benefits of strong typing go down significantly.

As far as Java goes, I love assertions. You can beat the hell out of your code, find all of the fringe cases that users figure out how to beat up your product, and after you think things are performing as they should, remove them with a compiler option. I don't think unit testing solves the same problem here, because in the really, really low level parts of my code I don't want to have nullchecks everywhere. If it is null, though, I'd like to slap the person who did it. Assertions do that.

Hiring a tester is money well spent, but not everybody's got that money to go around. Still, coding defensively helps testing a lot.

I like code reviews more because I don't like talking while working. When I'm "in the zone", I'm coding really quickly, trying out a lot of things, and putting off a lot of things as "will do later." It yields a lot of progress, and if I've got somebody critiquing my if-syntax or something else, it's slowing down the whole process.

I think it's split down the middle here, but pair programming, to me, is a waste of time. For some people it's great, but people work different ways. Code reviews afford me the same benefits (making sure things are consistent across features, making sure the correct libraries are used, pointing out pattern flaws) while allowing me to revisit my code and identify things as well.

Code reviews shouldn't be done by developers who aren't engaged in the code. If it's a half-assed review, it's a waste of time. It should be the reviewer's job to rape the code, find as many flaws (design, consistency, coupling, and security) as possible, with the subject doing the same thing. At least for me, it's one step to get the logic right, and another to get things in the right place.

Eric Anderson said...

I could see using assertions if there is some sort of rigorous beta testing that's being done by users. I'm just not sure how much value there is in the not null assertion right before you would make the reference that would cause the null reference exception. Assertion exception, null reference exception, the stack trace is going to point back to the same place.

I just don't like the noise that they create in the code. Perhaps it is just the type of applications that I'm dealing with. Where I'm interfacing with external data and services, then I prefer to clean up the nulls at the point of attack. After that, don't pass null and don't expect it.

This is probably just a matter of preference. Perhaps because now the majority of the meaningful code that I have written has been TDD, I haven't felt the compulsion to put in the assertions that others seem to want. So far, I haven't noticed this being a problem.

As to the pair programming, different people like different levels of feedback while they are pairing. I have teammates that don't like their pair to point out a problem until they get stuck for more than 60 seconds. So, I try to adjust to that. This really requires getting to know the other person and allowing for the fact that they like to work differently from you. You might find it interesting to compare team velocity with and without pairing. I am willing to allow for the fact that there are some situations where it is counterproductive. Having a team of people that really hate pairing would be one of those situations.