Pages

Wednesday, November 6, 2024

Can I discover how good is someone at TDD without watching them or looking at their code?

I've come to believe that the best way to interview a developer is to pair program with them, giving them a problem in their technical strength and being a questioning, supportive pair partner in order to see what their best is. And I want to make it a good experience for the candidate. The things I look for in a pairing session include, of course, TDD. Besides gauging their skill, I am looking for things like how they handle difficulty, if they can accept direction, how they handle disagreements, if they are curious, if they are careful and detail-oriented.And I want to see if they can have fun while working on something. 

If the candidate is not strong at TDD, I can help them learn and leave them with suggestions and resources on how to improve if they would like to apply again.

If the candidate is good at it, we can talk about TDD styles, or code patterns, or architecture or really anything relevant to the position. I'm looking to see their curiosity, and what they would. be like to work with.


Recently, a client asked me to participate in a panel interview for a senior developer they were considering. The panel was a mix of non-technical and technical people, so no pair programming was going to happen. I got to thinking how hard would it be to gauge whether a person' was good at TDD if their resume listed it as a skill. I proposed this to a colleague, D., who is learning TDD and we decided to see if I could do it. Here's a slightly edited transcript of what we did over instant messaging:


Jeff Hoover

Imagine you are Test Driving new functionality, say adding a student to a course, and I can't see your screen. Describe what I would see as you go from first test to first implementation to second test to implementation.

 

D.

I'd tell you that I'm assuming that the student and course classes exist, so that I don't have to go through the exercise of creating the "data bag" classes.  Then I'd tell you that I am going to assume that I'm creating a service class that will sit behind a controller or some other "exposing mechanism" so that I'm not having to deal with that nonsense...

 

Jeff Hoover

OK. So what do you type first in your editor?

 

D.

then I'd probably tell you that I'd start with a JUnit test case, and I'd say that I'd have a testAddingAStudentToACourse test method.  I'd tell you that.

 

Jeff Hoover

What goes in the test case?

 

D.

So the first thing that I do is something like: boolean studentAdded = registrationService.add(new Student());

 

(EDITORIAL - I would really like to hear: "To make sure my framework is configured properly. I'd write a test with assertTrue(false) , run the tests and see it fail, then I'd delete the test." Lack of this remark doesn't prove anything, but if they say it it gives me confidence in their experience with things that might go wrong, and that they can think about a process and describe it well)


Jeff Hoover

Does your test case compile? (Ed: I wish I'd figured out a way to get at this without handing to him. His answer doesn't give me much)

 

D

Nope.  So now I let the IDE generate the RegistrationService class.  Once that happens (and the add method is created as well) then the test fails.

 

(Ed. I probably should have asked why the test failed, to get at what assertion he had.


Jeff Hoover

Bonus point for IDE automation!

 

D.

Then I implement registrationService.add(Student student) { return true;}

 

D.

Then the test passes.

 

D.

Now for the next test, I realize that I've added a student, but haven't specified what course to add them to.  So now need to refactor the test and the class to add them to a course.  So now I have in the test:  boolean studentAdded = registrationService.add(new Student(), new Course());


(Ed. If I had asked: "let's say you don't have a Course class - would you stop working on the Service and take a side-track to TDD the Course, or stay with the Service and added tests to the Course as needed?", it might give a senior dev a chance to comment about outside-in vs inside out)



D.

Now I need to test that the course contains the new student after I register the student.  At least I think that's the right thing to do...

 

D.

That's the next smallest test that I can think of...

 

( Ed: Nice! Candidate has shown me a basic ability to TDD. If they were applying for a junior position, this would go a long way towards receiving an offer. Candidate has the basics. They will hone their skills when they pair program with senior developers on all their cards. For a senior position, more conversation would, of course, be necessary.)

 

D. 

I now have to look at whether or not the registration should return a boolean or an altered course object.  I'm gonna go with the latter.  So now, I change the return types, etc...



Thursday, February 15, 2024

Don't know where to start on TDD?


This post got me thinking about how difficult it can be for people new to Test Driven Development to know where to start. I started writing this post as a reply to the post, but I ended up 29 characters beyond the limit, so here it is:


You might start by coming up with an example of what you want your code to do, what its behavior should be. Here's an example:

"When I give [specific complex input] I will get [specific output]"

Then imagine this example sequence of test-writing:

1. a test that asserts that I can call newCode
2. a test that asserts that calling newCode returns nothing
3. a test that asserts that calling newCode with any argument returns nothing
4. a test that asserts that calling newCode with any argument returns six (or some other dummy result)
5. a test that asserts that calling newCode with simplest argument returns the correct result
6. a test that asserts that calling newCode with more complex argument returns correct result

Each test should fail when you first write it. Then you add implementation to newCode to make only that test pass. Don't add code to make future tests pass - remember, small steps. As you write implementations for subsequent tests, 1-4 will fail (maybe even to the point of not compiling). That's expected. Delete any test that is superseded by one that moves you closer to your goal.

You might not get to 6 with the same steps as me, or you might gain enough TDD experience to start at 2 or 3 or 4 instead of 1. But hopefully this example shows a means of tests "driving" behavior.


Friday, September 1, 2023

"Don't Make Me Think" about your code.

Sometime after 2008 or so, I started telling software crafters that I was coaching that I have a guideline for code quality: “Don’t make me think.” Often I used the phrase to reference one of the many well-known code smells. Recently, I wondered where I first learned that expression. Unfortunately, online searches are dominated by Steve Krug's 2000 UI/UX book (with editions in 2005 and 2013) by the same name.The earliest use that I could find of “Don’t make me think” as applied to code quality is a blog post from 2011, If you know of an earlier use, please get in touch.

As I thought more about "Don't make me think", I've come up with many examples. Here's my list so far. What's your favorite?

  1. Have a readme that tells me how to run tests and install and run your application
  2. Use a test naming convention that gives the action, initial state, and expected state.
  3. Name things well
  4. When possible, use the names that are the same as in the business domain
  5. No commented-out code
  6. No comments, except to explain something that you have to do in an unexpected way (Debunk “good” comments)
  7. Unless you squash, have meaningful commit messages. If you squash, have meaningful merge messages.
  8. Write short methods
  9. Write with idiomatic use of your language
  10. Don’t write “clever” code
  11. Don’t over architect or over generalize
  12. Have sufficient types and amounts of automated tests
  13. Maintain your tests as you maintain the rest of your code
  14. Methods should have few arguments, and probably no boolean arguments.
  15. The interface of a class should “hang together” without methods that “don’t belong”
  16. Avoid “primitive obsession”
  17. Find solutions besides switch statements
  18. Don’t go crazy with inheritance. Prefer composition.
  19. Use a rich domain model, not an anemic one.
  20. Avoid conditional nesting
  21. Avoid subclassing unless you need all (or most) of the parent class (avoid Refused Bequest)
  22. Declare things where they are used.
  23. Arrange Act Assert
  24. One assert/concept per test
  25. Avoid double negation
  26. Use a Formatting Convention
  27. Short feedback cycles
  28. Don’t mix levels of abstraction




Tuesday, September 29, 2020

Rough Post - Styles of Pair Programming

This post is probably a little rough, but I wanted to get it out, so here it is:


Fowler’s article on pair programming lists four "styles" of pair programming:

  • Driver and Navigator

  • Ping Pong

  • Strong-Style Pairing

  • Pair Development


and then goes on to throw out Pair Development as not being a style. Ping-Pong to me is totally a different style from anything else. There are also "Evil Pair Partner" and “Silent Ping Pong”, which are variants of PingPong, so I'd say there are three styles related to Ping Pong.


Fowler doesn't list "Just Pairing” as a style. Likewise, I can’t see the difference between “Just Pairing” and D/N - I consider them different names for the same one style.


The rule of Strong Style from Llewellyn Falco and Clare Macrae is: "For an idea to go from your head into the computer it must go through someone else's hands". I'll restate this as "Driver may not type their own ideas; driver must only type the navigator's ideas." That sounds like D/N with a restriction, so maybe a separate style.

All that said, I’m seeing five distinct styles:


  • Driver and Navigator aka “Just Pairing”

    • Strong Style 

  • Ping Pong

    • Evil Pair Partner (more for practice, not writing prod code)

    • Silent Ping Pong (more for practice, not writing prod code)



Saturday, June 22, 2019

Bangle - Next Steps

I haven’t solved the bridging issue yet, so I’ve started printing the first of two cut-down halves to see if I can glue them together seamlessly. (CA/Superglue is said to be good for this).


Here's the filament I chose. It's non-metalic PLA but looks a lot like copper:



If the result of gluing the two cut-down halves is good, I'll sand the inner diameter to make it a comfortable thing bangle, then print two full halves in copper and try gluing them. If gluing doesn't end up being a good solution, I'll have to solve the bridging issue, perhaps with supports, or with tweaking print/extrusion/fan speeds.


Saturday, June 15, 2019

Designing and 3D Printing a Chunky Bangle

Initial Concept

My wife likes big, chunky jewelry, so I thought I would try and 3D print something for her. I started with this as a basic idea:


This bangle looks to be in 3 pieces. My wife has similar multiple-part bangles. The pieces are held together with elastic. It allows for a smaller inner diameter, but the elastic is a potential point of failure. My wife also has one-piece bangles that she can slide her hand into, so I figure I can make a one-piece for her.

Tools

Printer: I have access to a Prusa i3 MK3, the predecessor to the MK3S.

Modeling: OpenSCAD - open source, "The Programmers Solid 3D CAD Modeller". Unlike traditional CAD apps that rely on drawing and/or moving around shapes, in OpenSCAD, you build designs by writing lines of OpenSCAD language code (some examples) Once you have a design in OpenScad, you have the software render it and export it in STL format.

Slicing / exporting gcode: Prusa has their own version of the popular open source app, Slic3r. Slicing is the process of taking a 3D model (usually an STL file) and converting it to a stack of slices that a printer can print, one on top of the other, to create the object. Gcode is the low-level instruction language used by printers to control the motions of the bed, the extruder, the arms and so on. Exporting gcode is the process of taking slices and converting them into those low-level instructions


Iteration 1:

Design a basic ring-ish shape. The easiest way I found was to design a disk and subtract from it a disk of same thickness but smaller diameter, centering the disks on each other. It looked like this:


Iteration 2:

I wanted to improve the ring design from a harsh geometric shape into the first inkling of a bracelet design. This involved learning to rotate a 2-D circle in 3-space around an axis, using the rotate_extrude() function. The result looked like this:


Iteration 3:

To create the first printed prototype, I reduced the thickness of the bracelet design, keeping the diameter. Thinness causes a quicker print and uses less plastic. Took about 40 minutes to print. (No pic)

Iteration 4:

Discover that diameter of first physical prototype is about 1/3 of useful bracelet diameter. Scale design to 300% and print again. When my wife put it on, it was a little too big, I decided to measure a favorite bracelet of hers and use its inner diameter (ID), 65 mm, for my design. Going back to the Iteration 1 design, I quick-printed something with the 65 mm ID. Holding it up against the favorite bangle, the ID matched. My wife put it on and called the fit perfect.  So, I had confirmed the final ID. (No pics)

Iteration 5:

Learn to go from circular cross-section to something more complex.
First, I set out to relearn parabolas:

https://www.symbolab.com/solver/parabola-equation-calculator
https://www.mathwarehouse.com/geometry/parabola/
https://www.mathwarehouse.com/quadratic/parabola/interactive-parabola.php
https://www.mathwarehouse.com/geometry/parabola/standard-and-vertex-form.php
https://www.desmos.com/calculator/dz0kvw0qjg
https://www.omnicalculator.com/math/parabola#how-to-use-the-parabola-equation-calculator-an-example
https://www.desmos.com/calculator/mey71rif1d


I created a curve that I liked. It turned out to be Y = 0.3*(x-6.1)^2 + c.


Then I enclosed the curve into a shape with a perimeter and area:


Then I plugged some points into the equation and added 3 corners. It was simple to turn those points into a polygon call in OpenSCAD (in a future iteration, I would calculate and add more points in between these points, to smooth out what was a faceted curve:

            polygon(points=[[6.0, 0.000],
                            [5.5, 0.108],
                            [5.0, 0.363],
                            [4.5, 0.768],
                            [4.0, 1.323],
                            [3.5, 2.028],
                            [3.25, 2.773],
                            [3.0, 3.518],
                            [3.0, 0.000]
                        ]);

The rotate_extrude() call stands up a shape and rotates it around the z axis. Applying this to my polygon resulted in a shape like a volcano:


In OpenSCAD, I duplicated the "volcano", fliped it 180 degrees, and lined the duplicate up with the base of the original:



I showed the double-volcano model to my wife for her blessing, which I received. A first real physical prototype is in sight.

Iteration 6:


I decided to test-print only one volcano, just to have something to handle before committing to the whole print (which the slicing software projected would take 13 hours at 7% infill). It turned out that because of the nature of the curve I chose, the edges taper to a very thin edge are are not comfortable to wear:




Iteration 7:

I decided I needed to find some way to smooth the sharp edges and reduce/eliminate the faceting artifacts shown above. I had already figured out how to reduce the faceting down the face of the "volcano" by using more points to express the curve that became the polygon that was rotated through space. The faceting around the perimeter turned out to be easy - I had to add an extra argument, $fn=360, (number of fragments) to the rotate_exclude() call. Of course, the extra computation changed the rendering and slicing time from seconds to several minutes. I smoothed the lips edges of the volcano by smoothing the polygon itself using the offset() function before the rotate_extrude() call. The resulting model looked like this:


I started to print, but not long after it started, the print separated from the bed. I suspected that there was not enough surface area on the bed, so I stopped the print and restarted it with a 15mm brim (seen below) After 13.5 hours, the first "real" prototype print looked like this:





But the print had some issues:





It might be because the sharp angle of the base of the upside-down volcano extends out too far for support good bridging. There are lots of ways to address bridging issues. But before I tried any of those, I noticed that the model had a weird asymmetry. Displaying each volcano in a different color: 


and shifting one of the volcanos along the x-axis make the asymmetry more obvious:



It turns out that before I used the offset(), the two halves would remain on the z plane when one was turned upside-down. But adding the call resulted in one of the volcanos impinging across the z-plane. Could the impingement be responsible for the defect?


Iteration 8:

I moved the offset()'ed volcanos so their bases both sit on the the z-plane, but that resulted in a "bumpy" circumference, so I didn't print it - it needed more adjustment:


Iteration 9:

By making each volcano cross the z-axis an equal amount, I got to  this:

To determine whether there is a bridging issue, but not have to print the entire 13.5 hours, I "chopped off" some of the top and bottom of the model (by intersecting it with a box)

I printed iteration 9 and the bridging issue remains:


 So, next is figuring out how to fix it.