I saw a passage attributed to Søren Kierkegaard that I might translate as:
The life of humanity could very well be conceived as a speech in which different people represented the various parts of speech [...]. How many people are merely adjectives, interjections, conjunctions, adverbs; how few are nouns, verbs; how many are copula?
This is a natural thing to ponder around my birthday. It's not a bad thing to ask myself more often: Which part of speech will I be today?
According to Darwin himself, in his autobiography:
I have no great quickness of apprehension or wit which is so remarkable in some clever men, for instance, Huxley. I am therefore a poor critic: a paper or book, when first read, generally excites my admiration, and it is only after considerable reflection that I perceive the weak points.
If you read my blog, you know this about me. Either you enjoy my occasionally uncritical admiration, or at least you tolerate it.
Last week some tweeted a link to Write code that is easy to delete, not easy to extend, an old blog entry by @tef from last February. When I read it yesterday, I was nodding my head so hard that I almost fell off of the elliptical machine. I have done that before. Trust me, you don't want to do it. You don't really fall; the machine throws you. If you are moving fast, it throws you hard.
I don't gush over articles in my blog as much these days as I once did, but this one is worthy. If you write code, go read this article. Nothing I write hear will replace reading the entire piece. For my own joy and benefit, though, I record a few of my favorite passages here -- along with comments, as I am wont to do.
... if we wish to count lines of code, we should not regard them as "lines produced" but as "lines spent".
This is actually a quote from EWD 1036, one of Dijkstra's famous notes. I don't always agree with EWD, but this line is gold and a perfect tagline for @tef's entry
Building reusable code is easier to do in hindsight with a couple of examples of use in the code base, than foresight of ones you might want later.
When OO frameworks first became popular, perhaps the biggest mistake that developers made was to try to write a framework up front. Refactoring from multiple programs is still the best way for most of us mortals to create a framework. This advice also applies to cohesive libraries of functions.
Aside: Make a util directory and keep different utilities in different files. A single util file will always grow until it is too big and yet too hard to split apart. Using a single util file is unhygienic.
Golf clap. I have this pattern. I am glad to know that others do, too.
Boiler plate is a lot like copy-pasting, but you change some of the code in a different place each time, rather than the same bit over and over.
For some reason, reading this made me think of copy-and-paste as a common outcome of programming language design, if not its intended effect.
Boilerplate works best when libraries are expected to cater to all tastes, but sometimes there is just too much duplication. It's time to wrap your flexible library with one that has opinions on policy, workflow, and state. Building simple-to-use APIs is about turning your boilerplate into a library.
Again, notice the role refactoring plays here. Build lots of code that works, then factor out boilerplate or wrap it. The API you design will be informed by real uses of the functions you define.
It is not so much that we are hiding detail when we wrap one library in another, but we are separating concerns: requests is about popular http adventures; urllib3 is about giving you the tools to choose your own adventure.
One of the things I like about this blog entry is its theme of separating concerns. Some libraries are perfect when you are building a common application; others enable you to build your own tools when you need something different.
A lot of programming is exploratory, and it's quicker to get it wrong a few times and iterate than think to get it right first time.
Agile Development 101. Even when I know a domain well, if the domain affords me a lot of latitude when building apps, I like explore and iterate as a way to help me choose the right path for the current implementation.
[O]ne large mistake is easier to deploy than 20 tightly coupled ones.
And even more, as @tef emphasizes throughout: It's easier to delete, too.
Becoming a professional software developer is accumulating a back-catalogue of regrets and mistakes.
When we teach students to design programs in their first couple of years of CS, we often tell them that good design comes from experience, and experience comes from bad design. An important step in becoming a better programmer is to start writing code, as much as you can. (That's how you build your catalog of mistakes.) Then think about the results. (That's how you turn mistakes into experience.)
We are not building modules around being able to re-use them, but being able to change them.
This is one of the central lessons of software development. One of the things I loved about OO programming was that it gave me another way to create modules that isolated different concerns from one another. So many folks make the mistake of thinking that objects, classes, and even frameworks are about reuse. But reuse is not the key; separation of concerns is. Design your objects that create shearing layers within your program, which make it easier to change the code.
It isn't so much that you're iterating, but you have a feedback loop.
As I blogged recently, competence is about creating conditions that minimize mistakes but also help you to recognize mistakes quickly and correct them. You don't iterate for the sake of iterating. You iterate because that's how you feed learning back into the work.
The strategies I've talked about [...] are not about writing good software, but how to build software that can change over time.
This blog entry isn't a recipe for writing good code. It's a recipe for creating conditions in which you can write good code. I do claim, though, that all other things being reasonably equal, in most domains, code that you can change is better code than code you can't change.
Good code isn't about getting it right the first time. Good code is just legacy code that doesn't get in the way.
That is a Kent Beck-caliber witticism: Good code is just legacy code that doesn't get in the way.
This blog entry made me happy.
I enjoyed this interview with Atul Gawande by Ezra Klein. When talking about making mistakes, Gawande notes that humans have enough knowledge to cut way down on errors in many disciplines, but we do not always use that knowledge effectively. Mistakes come naturally from the environments in which we work:
We're all set up for failure under the conditions of complexity.
Mistakes are often more a matter of discipline and attention to detail than a matter of knowledge or understanding. Klein captures the essence of Gawande's lesson in one of his questions:
We have this idea that competence is not making mistakes and getting everything right. [But really...] Competence is knowing you will make mistakes and setting up a context that will help reduce the possibility of error but also help deal with the aftermath of error.
In my experience, this is a hard lesson for computer science students to grok. It's okay to make mistakes, but create conditions where you make as few as possible and in which you can recognize and deal with the mistakes as quickly as possible. High-discipline practices such as test-first and pair programming, version control, and automated builds make a lot more sense when you see them from this perspective.
There is an open thread on the SIGCSE mailing list called "Forget the language wars, try the math wars". Faculty are discussing how to justify math requirements on a CS major, especially for students who "just want to be programmers". Some people argue that math requirements are a barrier to recruiting students who can succeed in computer science, in particular calculus.
Somewhere along the line, Cay Horstmann wrote a couple of things I agree with. First, he said that he didn't want to defend the calculus requirement because most calculus courses do not teach students how to think "but how to follow recipes". I have long had this complaint about calculus, especially as it's taught in most US high schools and universities. Then he wrote something more positive:
What I would like to see is teaching math alongside with programming. None of my students were able to tell me what sine and cosine were good for, but when they had to program a dial [in a user interface], they said "oh".
Couldn't that "oh" have come earlier in their lives? Why don't students do programming in middle school math? I am not talking large programs--just a few lines, so that they can build models and intuition.
I agree wholeheartedly. And even if students do not have such experiences in their K-12 math classes, the least we could do help them have that "oh" experience earlier in their university studies.
My colleagues and I have been discussing our Discrete Structures course now for a few weeks, including expected outcomes, its role as a prerequisite to other courses, and how we teach it. I have suggested that one of the best ways to learn discrete math is to connect it with programs. At our university, students have taken at least one semester of programming (currently, in Python) before they take Discrete. We should use that to our advantage!
A program can help make an abstract idea concrete. When learning about set operations, why do only paper-and-pencil exercises when you can use simple Python expressions in the REPL? Yes, adding programming to the mix creates new issues to deal with, but if designed well, such instruction could both improve students' understanding of discrete structures -- as Horstmann says, helping them build models and intuition -- and give students more practice writing simple programs. An ancillary benefit might be to help students see that computer scientists can use computation to help them learn new things, thus preparing for habits that can extend to wider settings.
Unfortunately, the most popular Discrete Structures textbooks don't help much. They do try to use CS-centric examples, but they don't seem otherwise to use the fact that students are CS majors. I don't really blame them. They are writing for a market in which students study many different languages in CS 1, so they can't (and shouldn't) assume any particular programming language background. Even worse, the Discrete Structures course appears at different places throughout the CS curriculum, which means that textbooks can't assume even any particular non-language CS experience.
Returning to Horstmann's suggestion to augment math instruction with programming in K-12, there is, of course, a strong movement nationally to teach computer science in high school. My state has been disappointingly slow to get on board, but we are finally seeing action. But most of the focus in this nationwide movement is on teaching CS qua CS, with less interest in emphasis on integrating CS into math and other core courses.
For this reason, let us again take a moment to thank the people behind the Bootstrap project for leading the charge in this regard, helping teachers use programming in Racket to teach algebra and other core topics. They are even evaluating the efficacy of the work and showing that the curriculum works. This may not surprise us in CS, but empirical evidence of success is essential if we hope to get teacher prep programs and state boards of education to take the idea seriously.
I ended up with an unexpected couple of hours free yesterday afternoon, and I decided to clean up several piles of old papers on the floor of my running room. Back when I ran marathons, I was an information hound. I wrote notes, collected maps, and clipped articles on training plans, strength training, stretching and exercise, diet and nutrition -- anything I thought I could use to get get better. I'm sure this surprises many of you.
There was a lot of dust to dig through, but the work was full of happy reminiscences. It's magical how a few pieces of paper can activate our memories. The happy memories leave in their wake a sadness, when my time as a runner ended. That's when the piles stopped growing. I stopped collecting material, because I wasn't running anymore.
Fortunately, the sadness of loss didn't drown out the happy memories. Instead, I started thinking about the future, which is really now. These thoughts are long past due.
Looking back through my running logs reminded of the pattern of my life as a marathoner. There was an ebb and a flow to the year. I trained for my first half marathon. Then I trained for my first full marathon. I ran lightly for a few weeks as my body recovered. Winter and spring saw regular runs, but a break for mind and body alike: no big plans, just enjoying the road. Then came the end of spring, and it started all over again: training for big races. These years were filled with variety in my running, variety in my goals.
The last few years have been different. I recovered from a couple of operations, eventually taking up the elliptical machine and returning to my bike for fitness. However, I have never become a cyclist in spirit the way I became a runner. I've been exercising lots, staying fit and healthy, but I miss the rhythm of running and training for marathons. In comparison, my exercise since leaves me bored and uninspired.
Diving into those piles of paper yesterday started me thinking, what are the next goals? I'll be working on that as we slide into winter, looking forward what next spring might bring.
A friend and fellow classic science fiction fan told me that one of his favorite books as a teenager was Robert Heinlein's The Door into Summer. I've read a lot of Heinlein but not this one, so I picked it up at the library.
Early in the book, protagonist Daniel B. Davis needed to make the most of the next twenty-fours. He located his car, dropped some money into the "parking attendant", set course, and relaxed as the car headed out into traffic:
Or tried to relax. Los Angeles traffic was too fast and too slashingly murderous for me to be really happy under automatic control; I wanted to redesign their whole installation--it was not a really modern "fail safe". By the time we were west of Western Avenue and could go back on manual control I was edgy and wanted a drink.
This scene is set December 1970; Heinlein wrote it in 1956. He may have missed the year in which self-driving cars were already common technology by 45 years or more, but I think he got the feeling right. People like to be in control of their actions, especially when dropped into hectic conditions they can't control. Heinlein's character is an engineer, so naturally he thinks he could design a better. None of my programmer friends are like this, of course.
It's also interesting to note that automatic control was required in the most traffic. Once he got into a calmer setting, Davis could go back to driving himself. The system allows the human to drive only when he isn't a danger to other people, or even himself!
Today, it is commonplace to think that the biggest challenges of the move to self-driving cars are cultural, not technological: getting people to accept that the cars can drive themselves more safely than humans can drive them, and getting people to give up control. It's neat to see that Heinlein recognized this sixty years ago.
Thanks to Greg Wilson for a pointer to this paper, which reports the result of an empirical evaluation of the effects of test-driven development on software quality and programmer productivity. In a blog entry about the paper, Wilson writes:
This painstaking study is the latest in a long line to find that test-driven development (TDD) has little or no impact on development time or code quality.
He is surprised, because he feels more productive when he writes tests up-front.
I'd like to be able to say that these researchers and others must be measuring the wrong things, or measuring things the wrong way, but after so many years and so many different studies, those of us who believe might just have to accept that our self-assessment is wrong.
Never fear! Avdi Grimm points the way toward resolution. Section 3.3 of the research paper describes the task that was given to the programmers in the experiment:
The task was divided into 13 user stories of incremental difficulty, each building on the results of the previous one. An example, in terms of input and expected output, accompanied the description of each user story. An Eclipse project, containing a stub of the expected API signature, (51 Java SLOC); also an example JUnit test (9 Java SLOC) was provided together with the task description.
Notice what this says:
The paper reports that previous studies of this sort have also set tasks before programmers with similar initial conditions. Grimm identifies a key feature of all these experiments: The problem given to the test subjects has already been defined in great detail.
Shocking news: test-driven design is not helpful when the design is already done!
As Grimm says, TDD helps the programmer think about how to start writing code and when to stop. For me, the most greatest value in doing TDD is that it helps me think about what I need to build, and why. That is design. If the problem is already defined to the point of implementation, most of the essential design thinking has been done. In that context, the results reported in this research paper are thoroughly unsurprising.
Like Grimm, I sympathize with the challenge of doing empirical research on the practices of programmers. I am glad that people such as the paper's authors are doing such research and that people such as Wilson discuss the results. But when we wonder why some research results conflict with the personal assessments of real programmers and our assessments of our own experiences, I think I know what one of the problems might be:
Evaluating the efficacy of a design methodology properly requires that we observe people doing, um, design.
An oldie but goodie from Andrew Tanenbaum:
Actually, MINIX 3 and my research generally is **NOT** about microkernels. It is about building highly reliable, self-healing, operating systems. I will consider the job finished when no manufacturer anywhere makes a PC with a reset button. TVs don't have reset buttons. Stereos don't have reset buttons. Cars don't have reset buttons. They are full of software but don't need them. Computers need reset buttons because their software crashes a lot. I know that computer software is different from car software, but users just want them both to work and don't want lectures why they should expect cars to work and computers not to work. I want to build an operating system whose mean time to failure is much longer than the lifetime of the computer so the average user never experiences a crash.
I remember loving MINIX 1 (it was just called Minix then, of course) when I first learned it in grad school. I did not have any Unix experience coming out of my undergrad and had only begun to feel comfortable with BSD Unix in my first few graduate courses. Then I was assigned to teach the Operating Systems course, working with one of the CS faculty. He taught me a lot, but so did Tanenbaum -- through Minix. That is one of the first times I came to really understand that the systems we use (the OS, the compiler, the DBMS) were just programs that I could tinker with, modify, and even write.
Operating systems is not my area, and I have no expertise for evaluating the whole microkernel versus monolith debate. But I applaud researchers like Tanenbaum who are trying to create general computer systems that don't need to be rebooted. I'm a user, too.
Last month, Seth Godin posted a short entry about reputation. It brought back memories of my first couple of years as department head. I was excited and wanted to do a good job, so I took on a lot. Pretty soon I was in a position of having promised more than I could deliver. Some of the shortfall resulted from the heavy load itself; there are only so many hours in a week. Some resulted from promising things I couldn't deliver, because I didn't understand the external constraints I faced.
When you don't deliver, explanations sound like excuses.
If I were giving advice to myself at the time I became head (already an adult who should have known better...), I would tell him to heed Godin's advice and help people learn what they can expect from you. Explicit attention to expectations can pay off in seeding reputation but also by setting parameters for yourself. Then live up to that standard.
If you teach people to expect little, perhaps unintentionally, they will -- even on the occasions when you do better. And after you get better, if you do, it takes a long time to undo the expectations you created early on.
Live the life you've taught people to expect from you -- but first be careful what you teach them to expect.
In the Paris Review's The Art of Fiction No. 123, Tom Wolfe tells how he learned about writer's block. Wolfe was working at Esquire magazine, and his first editor, Byron Dobell, had assigned him to write an article about car customizers. After doing all his research, he was totally blocked.
I now know what writer's block is. It's the fear you cannot do what you've announced to someone else you can do, or else the fear that it isn't worth doing. That's a rarer form. In this case I suddenly realized I'd never written a magazine article before and I just felt I couldn't do it. Well, Dobell somehow shamed me into writing down the notes that I had taken in my reporting on the car customizers so that some competent writer could convert them into a magazine piece. I sat down one night and started writing a memorandum to him as fast as I could, just to get the ordeal over with. It became very much like a letter that you would write to a friend in which you're not thinking about style, you're just pouring it all out, and I churned it out all night long, forty typewritten, triple-spaced pages. I turned it in in the morning to Byron at Esquire, and then I went home to sleep.
Later that day, Dobell called him to say that they were deleting the "Dear Byron" at the top of the memo and running the piece.
Most of us need more editing than that after we write anything, but... No matter; first you have to write something. Even if it's the product of a rushed all-nighter, just to get an obligation off our table.
When I write, and especially when I program, my reluctance to start usually grows out of a different sort of fear: the fear that I won't be able to stop, or want to. Even simple programming tasks can become deep holes into which we fall. I like that feeling, but I don't have enough control of my work schedule most days to be able to risk disappearing like that. What I could use is an extra dose of audacity or impetuosity. Or maybe a boss like Byron Dobell.