All of my database related posts are currently hanging out over at my blog on sqlity.net – http://www2.sqlity.net/blog/Dennis
When Unit Testing is not Enough
Experienced Java programmers will quickly recognize that the following is not how to check for String equality:
public class StringToConstantCompareDemo {
public static final String SOME_CONSTANT = "Hello";
public int compareStringToConstant(String input) {
if (input == SOME_CONSTANT) {
return 1;
} else {
return 0;
}
}
}
Instead this example checked if input is a reference to the same object as SOME_CONSTANT. Or as stated in the Java Language Specification:
“While == may be used to compare references of type String, such an equality test determines whether or not the two operands refer to the same String object. The result is false if the operands are distinct String objects, even if they contain the same sequence of characters. The contents of two strings s and t can be tested for equality by the method invocation s.equals(t).”
The code should have been:
public int compareStringToConstant(String input) {
if (SOME_CONSTANT.equals(input)) {
return 1;
} else {
return 0;
}
}
Okay, let’s say we know not to put an == for String comparison. We sometimes make mistakes though. One way to protect yourself from mistakes is to write unit tests.
What might a unit test for this function look like?
public class StringCompareTest {
@Test
public void stringIsEqualsToHello() {
StringToConstantCompareDemo demo = new StringToConstantCompareDemo();
Assert.assertEquals(1, demo.compareStringToConstant(
StringToConstantCompareDemo.SOME_CONSTANT));
}
}
If we ran this test, it would still pass with the == in the method. Why? Because we passed it a reference to SOME_CONSTANT. Let’s try again:
@Test
public void stringsAreEqual() {
StringToConstantCompareDemo demo = new StringToConstantCompareDemo();
Assert.assertEquals(1, demo.compareStringToConstant("Hello"));
}
Hmm…. it still passes. That seems a little odd. Let’s look at why.
During compilation, String constants of the same value are “interned”, or made to reference the same object in memory. See this section of the Java Language Specification for more details. This optimization helps preserve memory, but it foils the intent of our test case.
However, there’s one more thing we can try in our test case:
@Test
public void stringsAreEqual() {
StringToConstantCompareDemo demo = new StringToConstantCompareDemo();
Assert.assertEquals(1, demo.compareStringToConstant(new String("Hello")));
}
Now we’ve got it. With the == in the method, the test case fails and with .equals the test case passes.
The problem here is that if I knew about this behavior and thought to write new String(…) in my test case, I probably didn’t compare the String with == in the first place! Therefore, it’s unlikely that I would have ever found this bug with a unit test.
An alternative is to use a static code analyzer, such as PMD or FindBugs, which can locate this issue and many others. You shouldn’t expect a static analyzer to replace rigorous unit testing. They can only look for common programming mistakes that would be suspicious in any program. They cannot validate the logic of your code. However, unit testing and static analysis combined can detect a wider range of problems than either one alone.
Shutting down Tomcat when running with Maven-Cargo and JMX
Many use the Cargo Maven plugin to manage a web container during the build process. It allows you to install the container, deploy your application, start the container and then stop the container at some later point. Here are the relevant fragments of a typical Maven POM file which you might use to automatically start Apache Tomcat for running integrated tests:
[...]
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<executions>
<execution>
<id>install</id>
<goals>
<goal>install</goal>
</goals>
</execution>
<execution>
<id>start-container</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
<configuration>
<wait>false</wait>
</configuration>
</execution>
<execution>
<id>stop-container</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
<configuration>
<container>
<containerId>tomcat6x</containerId>
<zipUrlInstaller>
<url>${tomcat.distribution.url}</url>
<installDir>${tomcat.install.dir}</installDir>
</zipUrlInstaller>
</container>
<configuration>
<home>${tomcat.home.dir}</home>
<properties>
<cargo.logging>high</cargo.logging>
<cargo.servlet.port>8080</cargo.servlet.port>
<cargo.tomcat.ajp.port>8009</cargo.tomcat.ajp.port>
</properties>
</configuration>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>failsafe-maven-plugin</artifactId>
<version>2.4.3-alpha-1</version>
<configuration>
<includes>
<include>**/*Test*.java</include>
</includes>
</configuration>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
[...]
Let’s suppose that your application publishes MBeans for JMX . You may have integration test cases which connect to your application, running in Tomcat, through JMX to read the MBean properties. Therefore, you need to tell Tomcat that it should listen for JMX connections. This is done by modifying the Cargo configuration as follows:
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<executions>
<execution>
<id>install</id>
<goals>
<goal>install</goal>
</goals>
</execution>
<execution>
<id>start-container</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
<configuration>
<wait>false</wait>
</configuration>
</execution>
<execution>
<id>stop-container</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
<configuration>
<container>
<containerId>tomcat6x</containerId>
<zipUrlInstaller>
<url>${tomcat.distribution.url}</url>
<installDir>${tomcat.install.dir}</installDir>
</zipUrlInstaller>
<systemProperties>
<java.rmi.server.hostname>127.0.0.1</java.rmi.server.hostname>
<com.sun.management.jmxremote.port>10600</com.sun.management.jmxremote.port>
<com.sun.management.jmxremote.ssl>false</com.sun.management.jmxremote.ssl>
<com.sun.management.jmxremote.authenticate>false</com.sun.management.jmxremote.authenticate>
</systemProperties>
</container>
<configuration>
<home>${tomcat.home.dir}</home>
<properties>
<cargo.logging>high</cargo.logging>
<cargo.servlet.port>8080</cargo.servlet.port>
<cargo.tomcat.ajp.port>8009</cargo.tomcat.ajp.port>
</properties>
</configuration>
</configuration>
</plugin>
Note that we’ve added system properties to the container to tell how JMX clients can connect. If you run this however, you will be bit by the following exception when Cargo attempts to stop Tomcat:
[INFO] [cargo:stop {execution: stop-container}]
[INFO] [talledLocalContainer] Tomcat 6.x is stopping...
[WARNING] [talledLocalContainer] Error: Exception thrown by the agent : java.rmi.server.ExportException: Port already in use: 10600; nested
exception is:
[WARNING] [talledLocalContainer] java.net.BindException: Address already in use: JVM_Bind
[INFO] ------------------------------------------------------------------------
[ERROR] FATAL ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Failed to stop the Tomcat 6.x container.
Deployable [http://localhost:8080/cargocpc/index.html] failed to finish undeploying within the timeout period [120000]. The Deployable state
is thus unknown.
[INFO] ------------------------------------------------------------------------
[INFO] Trace
org.codehaus.cargo.container.ContainerException: Failed to stop the Tomcat 6.x container.
at org.codehaus.cargo.container.spi.AbstractLocalContainer.stop(AbstractLocalContainer.java:214)
at org.codehaus.cargo.maven2.ContainerStopMojo.doExecute(ContainerStopMojo.java:49)
at org.codehaus.cargo.maven2.AbstractCargoMojo.execute(AbstractCargoMojo.java:268)
at org.apache.maven.plugin.DefaultPluginManager.executeMojo(DefaultPluginManager.java:483)
at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoals(DefaultLifecycleExecutor.java:678)
at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoalWithLifecycle(DefaultLifecycleExecutor.java:540)
at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoal(DefaultLifecycleExecutor.java:519)
at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoalAndHandleFailures(DefaultLifecycleExecutor.java:371)
at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeTaskSegments(DefaultLifecycleExecutor.java:332)
at org.apache.maven.lifecycle.DefaultLifecycleExecutor.execute(DefaultLifecycleExecutor.java:181)
at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:356)
at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:137)
at org.apache.maven.cli.MavenCli.main(MavenCli.java:356)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.codehaus.classworlds.Launcher.launchEnhanced(Launcher.java:315)
at org.codehaus.classworlds.Launcher.launch(Launcher.java:255)
at org.codehaus.classworlds.Launcher.mainWithExitCode(Launcher.java:430)
at org.codehaus.classworlds.Launcher.main(Launcher.java:375)
Caused by: org.codehaus.cargo.container.ContainerException: Deployable [http://localhost:8080/cargocpc/index.html] failed to finish undeploy
ing within the timeout period [120000]. The Deployable state is thus unknown.
at org.codehaus.cargo.container.spi.deployer.DeployerWatchdog.watch(DeployerWatchdog.java:111)
at org.codehaus.cargo.container.spi.AbstractLocalContainer.waitForCompletion(AbstractLocalContainer.java:239)
at org.codehaus.cargo.container.spi.AbstractLocalContainer.stop(AbstractLocalContainer.java:208)
... 20 more
org.codehaus.cargo.container.ContainerException: Deployable [http://localhost:8080/cargocpc/index.html] failed to finish undeploying within
the timeout period [120000]. The Deployable state is thus unknown.
at org.codehaus.cargo.container.spi.deployer.DeployerWatchdog.watch(DeployerWatchdog.java:111)
at org.codehaus.cargo.container.spi.AbstractLocalContainer.waitForCompletion(AbstractLocalContainer.java:239)
at org.codehaus.cargo.container.spi.AbstractLocalContainer.stop(AbstractLocalContainer.java:208)
at org.codehaus.cargo.maven2.ContainerStopMojo.doExecute(ContainerStopMojo.java:49)
at org.codehaus.cargo.maven2.AbstractCargoMojo.execute(AbstractCargoMojo.java:268)
at org.apache.maven.plugin.DefaultPluginManager.executeMojo(DefaultPluginManager.java:483)
at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoals(DefaultLifecycleExecutor.java:678)
at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoalWithLifecycle(DefaultLifecycleExecutor.java:540)
at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoal(DefaultLifecycleExecutor.java:519)
at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoalAndHandleFailures(DefaultLifecycleExecutor.java:371)
at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeTaskSegments(DefaultLifecycleExecutor.java:332)
at org.apache.maven.lifecycle.DefaultLifecycleExecutor.execute(DefaultLifecycleExecutor.java:181)
at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:356)
at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:137)
at org.apache.maven.cli.MavenCli.main(MavenCli.java:356)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.codehaus.classworlds.Launcher.launchEnhanced(Launcher.java:315)
at org.codehaus.classworlds.Launcher.launch(Launcher.java:255)
at org.codehaus.classworlds.Launcher.mainWithExitCode(Launcher.java:430)
at org.codehaus.classworlds.Launcher.main(Launcher.java:375)
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2 minutes 29 seconds
[INFO] Finished at: Mon Jan 10 14:55:57 EST 2011
[INFO] Final Memory: 16M/371M
[INFO] ------------------------------------------------------------------------
This is because when Cargo attempts to stop Tomcat it launches a new JVM. This new JVM is configured with the same system properties as were used to start Tomcat. Which means it tries to start a second JVM with the same JMX ports.
In order to fix this problem, I’m going to take out the command to stop Tomcat from Cargo and then add an Ant task that closes Tomcat:
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<executions>
<execution>
<id>install</id>
<goals>
<goal>install</goal>
</goals>
</execution>
<execution>
<id>start-container</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
<configuration>
<wait>false</wait>
</configuration>
</execution>
<!-- This is where I intentionally removed the Cargo stop configuration -->
</executions>
[...]
<!-- This is the new Ant task for stopping Tomcat -->
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<phase>post-integration-test</phase>
<configuration>
<target>
<java classname="org.apache.catalina.startup.Bootstrap"
classpath="
${tomcat.install.dir}/apache-tomcat-6.0.26/apache-tomcat-6.0.26/lib/*;
${tomcat.install.dir}/apache-tomcat-6.0.26/apache-tomcat-6.0.26/bin/bootstrap.jar;
${java.home}/lib/tools.jar"
fork="true"
dir="${tomcat.install.dir}/apache-tomcat-6.0.26/apache-tomcat-6.0.26/bin">
<sysproperty key="catalina.home"
value="${tomcat.install.dir}/apache-tomcat-6.0.26/apache-tomcat-6.0.26" />
<sysproperty key="catalina.base"
value="${tomcat.home.dir}" />
<sysproperty key="java.io.tmpdir"
value="${tomcat.home.dir}/temp" />
<arg value="stop" />
</java>
<sleep seconds="10" />
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
You might have noticed that there’s an Ant task in there to sleep. I was doing something immediately after this in my build which needed Tomcat to be fully shutdown and putting a wait in there seemed to help. Ten seconds may actually be a bit excessive, and you might not need it at all.
Managing Agile Transitions – Part 1
The first book waiting for me to read when I got back from Argentina was William Bridges’s “Managing Transitions: Making the Most of Change”. In this series, I will provide a high-level overview of what the book is about and reflections on how it applies to the Agile transitions I have seen. Bridges is a well recognized consultant and author on the topic of organizational and individual change. “Managing Transitions” separates the task-oriented process of implementing a change from the human-oriented process of experiencing a change. By doing so, he elucidates how leaders can help their organizations adapt to change and emerge stronger, rather than being crippled by eroded morale and dysfunctional teams.
The model presented by Bridges identifies three overlapping phases of transition: the end of the old way, a neutral zone, and the beginning of the new way. He warns that people must progress through all three of these phases to successfully transition. Ignoring the personal process of transition leads to resentment and can result in attempts to circumvent the desired change.
The first phase involves letting go of the old ways of doing things. This begins with recognizing that in order to make a change, people must give something up. In order to do this successfully, leadership must identify who will loose what and recognize the impacts of those losses.
Considering an Agile transition, team members are often asked to give up quite a bit. Consider, for example, when they are asked to co-locate into a shared team room. Team members accustomed to cubicles, where they “own” their space, give up their privacy and the personalization of their workspace. Some developers view their cubicle as a home within a neighborhood of other cubicle homes. Even if they don’t work directly with their neighbors, they have strong bonds with them. In a sense, co-location can feel like being forced to leave a comfortable apartment to move in with a bunch of strangers.
Many developers are used to working individually. Writing, testing and integrating their code into the project; fixing bug reports when something goes wrong; reviewing requirements at their own desk and asking questions via e-mail; and so-on. The introduction of collective ownership can be a shock to these developers. Instead of comfortably reviewing and trying out their code by themselves before anyone else has a chance to see it (if anyone else was actually looking at it in the first place), their code is now public. If pair-programming is being introduced, their code is not only public, but now they are building it in tandem with another developer. Any private technique which they had doubts about is now utterly exposed, along with their thought processes and small mistakes they might make. The sense of personal risk of collective ownership can be extreme. Even if it can be rationalized as a safe step (i.e. everyone makes mistakes minor mistakes throughout the day, this is a great way to reduce defects, there are fantastic cross-training benefits, etc), the emotional rebuttal can be severe.
Finally (for today), consider the personal risks that team members are asked to take during retrospectives. Honestly evaluating how things went in the past iteration, being open to the impressions of other team members, and sharing publicly can feel like a major risk to people, especially if there has been bad experiences in the past. It requires members to have a lot of personal confidence and trust in their team. Team members are asked to give up their comfortable, familiar ways of doing their work in order to improve as a team.
Bridges proposes several strategies for the “ending” phase. First it is important to recognize and acknowledge to the team that there will be losses due to change – understand that people are being asked to give up something and this must be handled with sensitivity. Next, people must understand the problem which is motivating the change. He says that the old way of doing things still deserve respect – it is the past which got us to succeed up to this point, enabling the environment for change. Showing disrespect for the old way of doing things can alienate people and can be perceived as a personal attack. However, showing the current problems faced by the organization and how the change will help address these problems can motivate employees to make the change.
He also says that losses should be compensated for as much as possible. If someone is loosing something, give them something in return that relates to what they’ve lost. For example, provide a few private cubicles which newly co-located team members can use to take phone calls and check e-mail. Of course each team will value something different to compensate for their losses, as it will be experienced personally. The important part is to recognize this and listen intently to how individuals are experiencing those losses.
In the next post, we’ll discuss the neutral-zone phase of transition and see what happens between the old ways ending and the new ways beginning. For now, here are some questions for you:
- What do you feel you’ve had to give up during an agile transition or another organizational change?
- What might have helped compensate for your losses?
A Few Minor Bumps Setting Up PHPSlim
Tonight I succeeded in setting up PhpSlim which enables using Fitnesse and PHP together. A big thanks to Gregor Gramlich for building it and contributing to the community. I’ve been using Fitnesse for many other projects, and have been looking forward to using it with a new PHP project for quite some time.
Gregor’s PHPSlim installation instructions are excellent and I almost got the whole thing installed without a hitch. However, I wanted to share the following bumps I encountered. These all had to do with my own configuration, but someone else may hit the same things.
For each of these problems, it seemed like Fitnesse was hanging up in the browser forever. Eventually it would come back with a link to the errors.
1. In my local environment I have a few different setups with different php.ini files. Therefore, when I start php, I always have to remember to tell it the location of the correct ini file. If PHP cannot find your php.ini file, you will receive the following error:
The php_sockets module is not enabled. Please make sure that you have extension=php_sockets.dll in your php.ini.
You have no php.ini file defined!
If you have a similar problem, you will have to change:
!define COMMAND_PATTERN (php %m /path/to/project/Slim)
to:
!define COMMAND_PATTERN (php -c /path/for/ini/file/php.ini %m /path/to/project/Slim)
2. There were actually two errors there, and I also needed to update my php.ini file to enable php_sockets extension:
extension=php_sockets.dll
3. Finally, I received the following error message:
Fatal error: Call to undefined function mb_internal_encoding() in phar://phpslim.phar/PhpSlim.php on line 19
Call Stack:
0.0053 416944 1. {main}() C:\Programs\fitnesse\lib\phpslim.phar:0
0.0089 453472 2. require_once('phar://phpslim.phar/runPhpSlim.php') C:\Programs\fitnesse\lib\phpslim.phar:1131
0.0113 529232 3. PhpSlim::main() phar://phpslim.phar/runPhpSlim.php:4
and to resolve that, I needed to enable the php_mbstring extension in php.ini:
extension=php_mbstring.dll
After that, I was able to complete the first example in the tutorial and now I’m ready to explore some requirements using Fitnesse and PhpSlim!
Learning to teach software by taking tango classes
This year I had the opportunity to take a sabbatical between being a full-time employee and launching Curiously Correct. I spent the time in Buenos Aires, Argentina, exploring the country, learning a little Spanish, but mostly taking tango classes. I had the wonderful fortune of stumbling upon Dana Frígoli and Pablo Villarraza’s DNI Tango School shortly after I arrived in Buenos Aires and taking classes with their many stellar teachers. Quite honestly, it was among the best experiences of my life!
While I was sorting out the details of starting my own business, my mind was simultaneously being retrained to coordinate some intricate footwork, music and connection with a dance partner. However, what wowed me more than the my professors’ ability to dance was their ability to teach! At the end of each class, I took notes on what I was learning about tango, but also what elements of teaching I felt could be incorporated into my own training classes. Some of these lessons were simply reminders of what I had already known and was now seeing exemplified through very talented teachers, and others were lights of insight that have changed how I intend to approach training classes.
There were too many examples to fit into a single blog post – so I’ve put my top five here:
Change partners (cambia parejas) – During group classes, after a pair of songs everyone is asked to rotate dance partners – even if you came to class with a partner. As a social activity, this means you get introduced to a good number of new dance partners each class and I made it a habit of finding partners I hadn’t danced with at all before at every change. This has many advantages.
However, first consider how many times in a software training class you work in pairs. Pairing during the exercises of a software class has many of the same benefits – you learn from someone else, you have an added perspective and can discuss the material and if you get stuck, chances are at least one of you is vocal enough to ask the teacher what is going on.
So back to the advantages of rotating partners (pairs): Having multiple partners gives you a greater number of perspectives on the subject. One partner may have an excellent idea of how to write a test case, while another may come up with great designs. It doesn’t really matter if the partner is more or less experienced than you are while you are learning – everyone is both a teacher and a student in a each pairing. This applies to specific skills, personalities and styles of working.
Another advantage to rotating pairs is simply making new friends. In a professional training class, we might refer to this as networking. Making these types of contacts during a class can support the training by giving the student a broader community of people to discuss the lessons.
Change teachers (cambia profesoras) – Just like the added benefits of rotating pairings for a class, rotating teachers can provide the opportunity to learn from various perspectives and various teaching styles. Having exposure to a variety of styles is beneficial for students as we all having different ways of approaching a topic. A well-rounded set of tactics can reach us on a variety of levels. For example, I had one teacher who was great with analogies, another who was finally able to help me understand musicality a bit, and another who could gently fine-tune my stance and posture with subtle hints.
This is why I’m partnering with others to teach classes. For the upcoming Database Unit Testing classes, I’ll be pair-teaching with Sebastian Meine of sqlity.net. This will help the students gain multiple perspectives on the topic and be exposed to two different teachers during one class.
For Homework (para tarea) – I was frequently given “take-home” assignments from my one-on-one classes. Maybe I just needed that much extra work, but they really helped! It is helpful to remind the student to go and practice outside of class – however, this can be enhanced considerably by recommending a specific task. My instructors told me particular music tracks to practice with, exercises to do and how to practice particular steps when a partner was not available. For the student, building on their skills outside of the formal training is an important strengthening task. Having specific guidance on how to do that is invaluable.
Outside of university classes (where homework is the norm), I have only had a few professional trainers “assign” specific homework. These few assignments normally came in the form of book and website recommendations. Even more rarely was any specific advice given on practice material. Now I am keen on asking instructors about practice outside of class and will be incorporating it into my own training classes. In fact, if you check out the “Identifying Test Cases” series over at sqlity.net, you’ll see that I’ve begun assigning my SQL blog readers homework
Intense Guided Practice (práctica guiada intensa)– One of the models used by my tango instructors was to schedule private lessons as a one-hour class and two one-hour, intense practice sessions which exercised what was learned in class. This instruction technique alone accounted for a very rigorous style of learning where key concepts were not simply reinforced, but incrementally refined. Instead of finishing a given subject with a basic foundation, students finished it with demonstrable ability. This also provided a framework for utilizing different teachers in each student’s instruction – approaching the same topic from multiple perspectives.
This concept is in some degree quite prevalent on Extreme Programming teams in the form or pair-programming where developers can mentor each other while performing the actual job task. However it implies that someone on the team has enough experience to help the others refine their skills in the area of desired improvement. I’ve seen one other software/team trainer do this by holding workshops after a training session. Students could sign up for 1-2 hour blocks in small groups. These workshops became well-known as the most valuable portions of the class as they not only reinforced the learning, but helped the student integrate the course material into their own day-to-day work.
Teaching from the Heart (enseñar desde el corazón)– Unfortunately the expression has become so cliché that many have stopped paying attention. However, when you meet a group of teachers who all exemplify the notion of teaching from the heart – it returns as a powerful idea. The instructors I met at DNI are certainly excellent tango dancers and passionate about the subject, but moreover they are phenomenal teachers. They guide their students with patience. They understand that each comes from a different background and has different needs, comfort levels and abilities. Dana and Pablo have done an outstanding job recruiting and cultivating their team of teachers, and I feel very blessed for having had the opportunity to learn a little from them.
It always amazes me what we notice when we are trying to learn something else.
CppUTest in Visual Studio 2010 Express
If you download the latest version of CppUTest (2.1) and you are using Visual Studio 2010 Express you will find that the project files do not convert. You’re left with recreating the solution and project setup by hand. After fiddling with it for a little while, I was able to recreate the projects and get a clean build. I’ve made available for download the project files. Here are the steps to make this work:
1. Create a CppUTest folder somewhere on your drive.
2. Download CppUTest from sourceforge. Extract the zip file to the CppUTest folder.
3. Download these Visual Studio 2010 Express project files for CppUTest. Extract the zip file to the CppUTest folder.
4. Double-click the CppUTest.sln file in the CppUTest folder to launch the project in VS.
5. Press F7 to build the solution. This will create the CppUTest.lib file in the CppUTest\Debug folder. It also creates tests.exe and examples.exe in the same CppUTest\Debug folder.
6. From the command line, execute “tests.exe -v” to see the results of CppUTest’s self tests.
7. In your own projects, include the CppUTest.lib library to write your own unit tests.
Happy unit testing!
Code Review Techniques
There are two “myths” regarding code reviews which I would like to discuss here:
1. Code reviews are long, boring code walk-through sessions where either someone is nitpicking through irrelevant code details or most of the group has glazed over and is trying to keep awake.
2. Code reviews are largely unnecessary because pair-programming sufficiently accomplishes all code reviews.
Let’s first talk about why code reviews are helpful:
1. They contribute to a shared understanding of the project.
2. They help find defects.
3. They help find ways to improve the design.
Pair programming actually contributes excellently to all three of these goals. Furthermore, rotating pairs on the team helps share the project knowledge faster. In fact, I think pairing contributes to more than 80% of a team’s code review needs.
However, there is that last bit that’s missing from pairing. That is the opportunity for the team to read and openly discuss it’s own code as a group. One of the fundamental ways to become a better programmer is to read more code. A great amount can be learned by discussing the code as a whole team.
So how do we conduct a more successful code review – one that’s not laboriously boring but still productive to improving the design and removing defects?
1. Team members each nominate a piece of code to review. It can be something worked on recently, a suspicious area of the code, or an area that the team knows will be changed in the near future. The piece of code should be small enough to discuss in the time available for a code review.
2. Gather the team together. Ask the team to be open-minded to having their code discussed publicly. Remind the team that this is a professional review with the purpose of improving the product, not to cut each other up.
3. The team quickly votes on which code nominated they feel energy toward reviewing.
4. Each member of the team is given the opportunity to read through the code piece selected, including any unit tests or other artifacts directly related to the code piece.
5. The team discusses the code.
6. New unit tests may be added during the session when:
a. a question is raised about how the code functions under a given circumstance
b. a potential defect is found
c. a missing feature is discovered
7. The team may during this session white-board different design options, suggest refactorings or check that code is properly tested.
8. Agreed upon changes are made immediately during the session, re-executing the tests for each change. Allow the team to experiment here with potential options. If the result ends up worse than what you started with, simply through away the changes. At least something was learned in the process.
To summarize, pair programming provides most of what is needed from required code reviews. However, reading, discussing and changing code as a team in a code review session is still a vital aspect to knowledge sharing, design improvement and defect prevention.
Increasing your Development Skills
To become a great developer, it’s important to spend time learning the craft of development. There are many ways to do this, and I’ll humbly offer a few here.
Schedule Learning Time
It takes time to improve your skills and if you don’t make it a priority, it just will not happen. Be proactive, schedule time for yourself each week or everyday to learn. Remember, the time spent learning now will pay itself back in the long run.
Be a Better Tester
One of the most important skills of great developers is testing. Testers are experts at finding ways software breaks or misbehaves. As a developer with good testing skills, you will be better equipped to prevent defects in the first place and write code that fails gracefully.
Practice identifying boundary test cases, input and output test classes, and exception test cases. Learn how to write automated tests can conduct exploratory testing sessions. Spend time pair-testing with an experienced tester to learn their techniques and try out your own. Familiarize yourself with automated testing tools to improve your productivity.
Pair Program
Pair programming is valuable for creating a shared understanding of a project’s structure and reducing defects. Furthermore it is valuable as a learning tool to become a better developer. A great amount can be learned from sitting with another developer and working on the same task. Almost anything can be learned during a pair-programming session including new design patterns, algorithms, time-management, error-handling, tools or simply even keyboard shortcuts. Any of these can all be learned from pairing even with less experienced developers than yourself. It only takes an open mind and another programmer.
Try Something New
Try out new tools, programming languages and libraries. Even if you decide that you cannot use a new tool right away, having encountered it and gained some experience with it adds to your toolbox for later challenges. I can’t count the number of times I’ve dabbled with a tool, put it on the shelf because I had no need for it now, and then found it to be the perfect solution for a problem I encountered a few months later. If I didn’t dabble with it then, I would have had no idea to apply when it was needed.
Learning new programming languages and libraries works very much the same way. You might not need it now, but a new language may be the perfect solution to a problem in the near future. Furthermore, learning a new programming language exposes you to new paradigms – new ways of approaching problems. When I first started learning C++ (long ago and far away now), it had profound impact on my functional programming. It taught me to organize my code differently and pay closer attention to re-use and encapsulation. A little more recently, when I was learning Spring, I was subtly learning new ways of structuring my code and gaining a deeper appreciation of dependency injection.
Read Code
Grab some code samples from other developers or from open source projects. Ask yourself questions about the code, such as: did they solve a problem in a novel way? would you have done something differently? are there any bugs looking back at you?
Read …
Books, blogs, magazines, etc. Even ones not directly related to writing code (although those are good also). Don’t limit yourself to books on your specific language or application area. Instead expand out into general programming practices, project management, self management, testing, requirements and usability. In other words – pick up something that you would not have picked up before and see what it has to offer.
Find a Mentor
Is there a another developer you look up to or feel you could learn a lot from? Ask them to be your mentor. Mentoring does not have to be highly structured, and it can be an invaluable way to learn and build relationships. Suggest meeting for an occasional lunch, pairing on a particular feature, reviewing some code together or setting up a learning plan or recommended reading list.
Share
Writing down what you’ve learned is an excellent way of reinforcing the lesson. That and it helps others learn also. You can also share by presenting at local user groups, mentoring less experienced developers and participating in mailing lists in the development community. Furthermore, you can contribute to open source projects.
Speaking of sharing – feel free to comment and tell me what techniques you’ve found useful for becoming better in this craft.