Journeyman Developer

Whatever that means

Communication: The Undeveloped Skill

| Comments

As a profession, we are not known for our communication skills. Logical computers are so much easier to understand than emotional people. To fully excel, though, we must be able to do both effectively.

Most outsiders view us as interpreters, translating descriptions into a form computers can understand. This is not all we do, but developing this skill will make several other aspects of our job easier.

Why Communication Is Important?

Requirements are usually vague. We actually need to translate ideas into a form computers understand. A well placed conversation can improve our understanding of the goal, which in turn may transform a complex solution into a simple one.

Early in my career, I was asked to convert a list of strings into an html table with the elements sequenced vertically. HTML tables are much easier to sequence horizontally than vertically. Those were the requirements, though, and I spent a good deal of time making them work.

Several days later, I was chatting with the author of those requirements. I commented on the difficulty, it turns out that he didn’t actually care about the direction. I could have easily avoided that effort with a five minute conversation.

Accept Feedback

I’m always right. I know my idea is going to work, I’ve got a vision and all variant suggestions be damned.

This is extremely dangerous. We need to accept that there might be flaws in the idea, and listen to the feedback describing those flaws. We don’t want to spend weeks building something, only to discover it was the wrong thing.

Pretotyping, or doing the bare minimum to prove the idea will work, is a great technique in addressing this problem. We want to create the possibility of feedback as early as possible.

Get Along

Enemies accumulate and make life way too hard. It may seem reasonable to piss a few people off now to get something done, but it will make future interactions harder. And there will be future interactions, the interest rate on a loan against our reputation is always too high.

How Can We Get Better?

The Software Craftsmanship movement says that we should practice outside of work to hone our skills. Toastmasters is an organization dedicated to just that. I’ll be joining in August, and I suggest you do too.

Measuring Readable Code

| Comments

Ever wonder how readable people find your code? Do you wish there was a more objective way to measure this?

Now there is. Introducing Readable Code, a site targeted at measuring the time it takes to understand a piece of code.

How It Works

  • Visit the site
  • Read the sample presented
  • When you understand what the sample is doing, click “I Understand This”

The time it took to understand the sample will be recorded, and you will be taken to a page averaging all of these times.

Call For Help

As you may be able to tell, the site is currently more of a proof of concept than a valid experiment. Try it out and let me know what you think in the comments of this post.

Also, I’d like to add some sort of qualitative measure, something to gauge the level of understanding, rather than just the time, let me know in the comments. I’ve had a couple of ideas, but most of them add more complexity to the design than I’m happy with.

Motivation

There are a lot of people in the software development community which are loudly asserting contradicting advice on what makes code readable. I’m not sure what the answer is in a lot of these cases, and am tired of these people arguing without objective data. My hope is that, by providing a place for these ideas to be measured, we can finally get an objective answer.

TDD and Mathematics

| Comments

I’ve started going through SICP to try to learn Clojure in a little more depth. I’m quickly realizing, though, that since some of the exercises are on the theme of “implement this mathematical equation”, it’s difficult to do so using TDD.

Exercise 1.8

Newton’s method for cube roots is based on the fact that if y is an approximation to the cube root of x, then a better approximation is given by the value

Use this formula to implement a cube-root procedure analogous to the square-root procedure.

Approach

good-enough?

Because this function provides approximations, not exact values, we first need to build a function to tell us a value is good enough. This will certainly be needed in the tests, as our function isn’t going to return exactly 2 when 8 is given. What’s interesting is we’ll also need it in the main code, to know if we’re close enough to stop.

Exact values will be easier to build, so we’ll start with the usual base case: 0

1
2
3
4
(deftest good-enough?-test
  (good-enough? 0 0))

(defn good-enough? [guess n])

That’s pretty braindead, so let’s add an assertion

1
2
3
4
5
(deftest good-enough?-test
  (is (good-enough? 0 0)))

(defn good-enough? [guess n]
  true)

Now let’s get rid of that hard-coded true:

1
2
3
4
5
6
(deftest good-enough?-test
  (is (good-enough? 0 0))
  (is (not (good-enough? 1 0))))

(defn good-enough? [guess n]
  (= guess n))

We’re trying to find the cube root, not equality:

1
2
3
4
5
6
7
(deftest good-enough?-test
  (is (good-enough? 0 0))
  (is (not (good-enough? 1 0)))
  (is (good-enough? 2 8)))

(defn good-enough? [guess n]
  (= (math/expt guess 3) n))

And it usually won’t be an exact match:

1
2
3
4
5
6
7
8
(deftest good-enough?-test
  (is (good-enough? 0 0))
  (is (not (good-enough? 1 0)))
  (is (good-enough? 2 8))
  (is (good-enough? 2.000001 8)))

(defn good-enough? [guess n]
  (< (- (math/expt guess 3) n) 0.0001))

But the tolerance is faulty, anything less than the target will pass:

1
2
3
4
5
6
7
8
9
(deftest good-enough?-test
  (is (good-enough? 0 0))
  (is (not (good-enough? 1 0)))
  (is (good-enough? 2 8))
  (is (good-enough? 2.000001 8))
  (is (not (good-enough? 0 8))))

(defn good-enough? [guess n]
  (< (math/abs (- (math/expt guess 3) n)) 0.0001))

next-guess

Here’s where we build the core of the algorithm, the logic to generate a new guess. The idea here is to build it up incrementally by making as much of the expression as possible go to 0.

We’ll start by making $\frac{x}{y^2}$ go to 0. Ideally, we would also like to make $2y$ go to 0, but that would cause a division by 0 error.

1
2
3
4
5
(deftest next-guess-test
  (is (= (next-guess 1 0) 2/3)))

(defn next-guess [guess n]
  2/3)

Now another value to triangulate the functionality:

1
2
3
4
5
6
(deftest next-guess-test
  (is (= (next-guess 1 0) 2/3))
  (is (= (next-guess 2 0) 4/3)))

(defn next-guess [guess n]
  (/ (* 2 guess) 3))

Let’s start working on the next term. Notice that by keeping the denominator of this term to 1 we get to drive the numerator separately:

1
2
3
4
5
6
7
8
(deftest next-guess-test
  (is (= (next-guess 1 0) 2/3))
  (is (= (next-guess 2 0) 4/3))
  (is (= (next-guess 1 1) 1)))

(defn next-guess [guess n]
  (/ (+ n (* 2 guess))
	 3))

Finally we can finish off the equation:

1
2
3
4
5
6
7
8
9
10
(deftest next-guess-test
  (is (= (next-guess 1 0) 2/3))
  (is (= (next-guess 2 0) 4/3))
  (is (= (next-guess 1 1) 1))
  (is (= (next-guess 2 1) 17/12)))

(defn next-guess [guess n]
  (/ (+ (/ n (math/expt guess 2))
		(* 2 guess))
	 3))

cube-root

Now that we have a reliable way to tell if our answer’s good enough, and a way to determine the next guess, we can start driving the main function:

1
2
3
4
5
(deftest cube-root-test
  (is (good-enough? (cube-root 1) 1)))

(defn cube-root [n]
  1)

We’ll need a way to track our current guess, so this creates the need for an extra argument:

1
2
3
4
5
6
7
(deftest cube-root-test
  (is (good-enough? (cube-root 1) 1))
  (is (good-enough? (cube-root 1 1) 1)))

(defn cube-root
  ([n] 1)
  ([guess n] 1))

Now we can refactor the duplication out of the two forms:

1
2
3
(defn cube-root
  ([n] (cube-root 1 n))
  ([guess n] guess))

The next test requires a little bit of algebraic manipulation. I don’t want to get the next guess and start recursing in the same step, so I need to figure out a guess that will require another step, but only one.

Plugging this into a polynomial solver yields -1/2, which will suit our purposes:

1
2
3
4
5
6
7
8
9
10
11
(deftest cube-root-test
  (is (good-enough? (cube-root 1) 1))
  (is (good-enough? (cube-root 1 1) 1))
  (is (good-enough? (cube-root -1/2 1) 1)))

(defn cube-root
  ([n] (cube-root 1 n))
  ([guess n]
	 (if (good-enough? guess n)
	   guess
	   (next-guess guess n))))

Now we want to drive the recursion, so let’s test something that requires more than one guess:

1
2
3
4
5
6
7
8
9
10
11
12
(deftest cube-root-test
  (is (good-enough? (cube-root 1) 1))
  (is (good-enough? (cube-root 1 1) 1))
  (is (good-enough? (cube-root -1/2 1) 1))
  (is (good-enough? (cube-root 8) 8)))

(defn cube-root
  ([n] (cube-root 1 n))
  ([guess n]
	 (if (good-enough? guess n)
	   guess
	   (cube-root (next-guess guess n) n))))