Here is a report of how the US Navy is using a 3D simulation environment, written in Squeak, to evaluate submarine console designs.
This is going to be a small rant. Before I begin I should set a context. I’ve been programming computers now for more than 30 years. And I’ve had the good fortune to have paying jobs doing Object Oriented Programming (OOP) in Smalltalk for over 20 of those years. I enjoy this work very much.
Furthermore, my experiences in programming has been relatively broad in that I have worked on financial applications, insurance applications, development utilities, industrial process controls, point-of-sale kiosk software, Virtual Machine-level troubleshooting, entertainment software and applications delivered in multiple human languages. Once even in Kanji, but I can not read a work of Kanji. I’ve also conducted workshops and seminars, provided many mentoring and tutoring sessions, and have worked as an Adjunct Professor teaching programming skills.
But this is not going to be a rant about “How we used to do things.” I’ve noticed as I work with teams developing software there are a few common problems and behaviors encountered regularly.
Knowing that I have an “astigmatism” because of my deep involvement in the Smalltalk community, I have a tendency to view many aspects of programming through the lens of an Object Oriented Programming developer. I see things in a very Smalltalk-centric way and I’m discussing things seen on Smalltalk projects. However, I’m guessing there is a level of universality.
Complex problems to model
It doesn’t matter if we are talking about financial settlement models, high speed process control systems, high speed transaction systems, insurance billing models, serving up reports over the web or whatever. These are complex systems. These problem domains are often loosely defined and changing throughout the development cycle. Complex problem domains often include nuances and subtlety seldom encountered in everyday programming projects.
Smalltalk is used to handle complex problems
Smalltalk is often used to handle complex problems because it can do it so well. Smalltalk makes modeling complex systems simple. For both Object Oriented Programming and Smalltalk, I can say that I have never worked with a more productive environment that allows programmers to tackle difficult problems with rapid reliable results.
Handling the complex well is one of Smalltalk’s strengths. And here’s the really cool part. Smalltalk syntax is dead simple and easy to read.
While it’s true it may take years to acquire the skills of a more seasoned Smalltalk developer, folks with a decent amount of Smalltalk literacy (and I’m talking about what you can learn in a weekend) can make meaningful contributions to a team project even as newbies with proper coaching and mentoring available.
That’s the beauty of Smalltalk. It provides a rapid reliable development environment for complex problems in a an approachable manner. Smalltalk is not “file-centric” like some of the so-called modern programming languages, so that takes a little getting used to. You do almost everything inside code browsers, inspectors and even develop within the debuggers. The language itself is easy to learn.
The class library of Smalltalk, refined and developed for over 30 years, is both rich with capability and daunting to approach for a new-commer. You could probably make that statement about any rich library of information, even those outside of programming. The skills required are learning how to look for things, how to recognize their value and where and when to apply what you know. But you also can start out doing quite powerful things after grasping just a few key core Classes in the library.
It also evident that complex models are created to solve complex problems. Keep this in mind. Smalltalk helps to make the complex easier to express and model. Smalltalk code can be very simple to read and understand.
That’s not the same thing as saying you can write code without careful thought and study. Remember we are modeling complex problems with powerful software.
In addition to good Object Oriented Programming concepts, most Smalltalk development teams use SUnit. This is the testing framework behind the modern concepts of Test Driven Development (TDD). It’s roots began in Smalltalk and are often integrated into the everyday development processes the Smalltalk teams use to deliver quality code.
I’ve become a strong advocate of this approach, recognizing it’s strength’s and weaknesses, and consider TDD an essential component for delivery of quality code.
Development team composition
Modern software development teams are not composed from a full compliment of strictly seasoned programming veterans. In fact, it’s been my experience that teams with limited diversity in experience are often the worse performing. A full team of “Type-A” individuals will have a high level of both churn and drama. A good team has a mix of high-end talent, less experienced programmers, and developers with good business domain skills. It works just fine if the domain experts also have the least Smalltalk programming skills. Over time domain knowledge and programming knowledge will grow for the entire team.
Usually the more seasoned the developer, the lighter and cleaner the code is. A seasoned developer will write less code and often deletes code. I know, that seems contradictory.
It takes very little time to learn how to develop in Smalltalk and become a contributing member of the team. It takes a little more time to develop decent Smalltalk literacy skills, where the developer can browse code and quickly understand what is really going on across multiple collaborating objects. It can take quite a while before the developer begins to leverage well the deep Class library of code available.
A problem occurs when Smalltalk developers without strong code literacy skills (reading and comprehension) are writing enhancements for or fixing bugs in existing code. They often don’t catch the nuances in the code. But it’s often not their fault.
Let me draw out an example or two.
Example 1. The relatively new developer correctly determines that the root cause for a problem is code that exists in a common super-class. So he or she begins to modify code in the super class without really noticing how the other subclasses use it. Things could break. Hopefully the team has constructed a strong suite of SUnit Tests that will catch if a programmer inadvertently breaks something in this manner. But it can be difficult to detect.
The developer should apply code refactoring to break out unique behavior amongst the subclasses. A new developer should not be expected to have those refactoring skills. That takes time and training. Mentoring or “just-in-time” pairing helps.
Example 2. Smalltalk is a “late-binding” environment. That is, the “type” of an object is often irrelevant if it is passed as an argument along with a message. The receiving object will handle whatever it gets. Sometimes a developer will attempt to simplify method arguments so they are always assumed to be of one specific kind. Sometimes this is done to an extreme level. For example, an Array object is a kind of Collection object. You can ascertain it’s size (dimension) and you can access it’s components by iterating over it with indexes. However an Array object is a fixed size. An OrderedCollection has many of these same properties, also inheriting from the Collection class hierarchy, but you can change it’s size by adding and removing items. If the developer starts writing code assuming the argument is always an Array, a consumer of this method may discover they can no longer use it’s service for a more flexible Collection type. It breaks.
The developer should examine the known senders of the message and see how it is used. Also, it’s almost always the case that correct behavior in a method allows for the more general object to be accepted as an argument. New developers often don’t realize the subtlety and power that comes from polymorphism.
Example 3. We are often taught that comments in code are a good thing. Certainly throughout our school years we’ve probably even gotten feedback that not enough clear comments exists in our code.
I tend to take the opposite view. A comment is a sign that something hard to understand is happening in the code. The correct approach, in my opinion, is that the code should be made self evident. Add a comment if you need to alert the developer to something, but do it sparingly. Comments never tell the computer what to do. A comment is a note to a human being.
Time out for a humorous story.
A Computer Science student with excellent skills and grades volunteers to help out at the computer lab at school as a lab assistant to help newer programming students work on their assignments. One day a very frustrated student approaches the lab assistant with computer printouts in hand and, exasperated, exclaims that he just cannot get the program to work. The lab assistant begins to pour over the program listing. While examining the code, the new programming student exclaims, as a way of demonstrating how frustrated he is, “I get the idea that the compiler is ignoring all my comments.”
Comments never tell the computer what to do. The programming statements do. So why am I writing about this? When I find code that has a lot of comments in it I get curious and begin to review it for possible flaws in the logic. The programmer obviously thought it was difficult to write and understand. It’s a tip-off for me. I once even saw a comment that said “I pray to God this works.”
Oh yeah. That will work. A new kind of development model: Divine Intervention Development (DID).
Other team factors
Our industry is also loaded with egos. That’s easy to understand. It often takes an exceptional mind to break down the complexities of the real world and model them in manageable software. Math Majors and Engineers often make great programmers. Our industry tends to collect individuals who really think about things. We call ourselves “Analysts” for good reason. And it’s also easy to see that programmers can have both a narrow approach to things and how they see the world.
The problem with this is that it can show up in the code in ways it shouldn’t. How many times have you seen a piece of code and you say “That’s brilliant”? The cool part is when you realize the brilliant code is often also dead simple.
Cleverly written code
Yet we also encounter code everyday where the programmer was just being “clever”. I have nothing against clever programming. But here’s the trick.
The code has to be “smart” and simple. If you are doing some trick in the code because of a technique you’ve seen elsewhere, stop and think about this for a moment. What kind of team do you have? If you have a lot of newbies on the team, it could be an un-smart choice.
Keep things simple. Make it obvious what you are doing. Rarely do we really write code that has to be constructed as if we were writing in “C” or Assembler. Make the flow of the algorithm obvious, perhaps even including temporary variables in intermediate steps. Don’t waste everyone’s time writing highly optimized, fast, code. Write maintainable code. Write something that can be read quickly and understood immediately.
Set the example
Training takes time. If you are an experienced developer, remember that the new coders are likely going to be browsing your code and reusing it. Often, that’s actually where some of the brittleness comes in our projects. Rather than reading the code to understand exactly why it is written the way it is, a new coder will often journey on a “cut-and-paste” raid, grabbing pieces from anywhere to make something work.
That’s okay as a first pass at a solution. But take the time to go back and revisit these situations. You probably have an excellent opportunity to refactor code and probably redesign. If behavior is being replicated in multiple places throughout your project, manifest as little repeating snippets of code, that’s a sure sign there are one or more objects missing from your design. Search for these gems and see if you can remove code by elevating the behavior to a richer object.
And don’t shy away from using First Class objects. We’re not coding in “C” here and passing around lots of strings as arguments. If you have a first class object that has powerful behavior, use it.
Strong unit tests
Another area that takes real time to develop skills, is in understanding how to write unit tests with good code coverage. What I’m looking for is a test suite that exercises the model in as many permutations as you can imagine. Yeah, that’s often tedious work. But it protects you from later damage done by someone, including you, fixing a problem without realizing it’s impact on other parts of the system. If there are combinations of inputs that can exercise a set of Boolean logic in your model, construct a “truth table” of inputs and outputs for those combinations and codify it in a unit test. Lock the behavior of your objects down.
Refactor by design
I’m making a case here for building-in a level of expected refactoring in code and design. We’re all human and our knowledge about a domain evolves over time as we learn more about it. This is often the root to some of the “change” that can be so difficult for programming teams to adopt. The team may institute a change. The customer may request a change. Accept this as a natural effect of how our knowledge grows. Changes in requirements are actually a good thing. Changes can cause you to revisit your assumptions and realize how to make a richer more flexible model. You shouldn’t be expected to be aware of all ending conditions when you start a project and neither should your customer.
Tactical and strategic responses to issues
There is one down side to rapid response that I have seen that troubles me. I know, you may be wondering how rapid response of any kind may be bad. This shows up in at least a few forms I can think of while writing this.
Don’t strictly reward quickly fixing bugs. Sure, we want bugs fixed quickly. But here’s the thing. If the development team becomes “trained” at quickly finding the errant code piece, correcting it and then shipping it, an opportunity is lost and a mistake is being made. We are rewarding the wrong behavior. Often bugs are universal. What was coded wrong somewhere was probably replicated elsewhere. Count on it. To some extent this shouldn’t actually happen. I believe the best projects are where code is never duplicated across classes. Write it in one place. Then if you find a bug you know you fixed it everywhere. Sometimes the bug was really just a change in the design. These are more subtle and difficult to find when they exist in other places in the code.
Smalltalk will help you
Smalltalk provides outstanding tools to assist you in finding similar code. Look for other implementors of the same method name. Look for similarly named methods. Look at senders of all of those methods. Learn to develop good browsing skills in your team. Reward finding similar bugs and eradicating them by reducing code first.
Also, when you find a bug, immediately begin thinking about how you can construct a unit test to demonstrate the bug exists before you even fix it. This way, after you fix it, it should never re-appear.
If the team ignores the need to be vigilant about code refactoring, you will build “software debt”. These are places in the code where you know things are brittle, and given other conditions, will fail. Refactoring should be a natural built-in part of the development process. If you are developing in quick iterations, consider adding a short iteration where the team does nothing but refactor code.
I tend to follow the process that says, “Write it to work first, have strong unit tests to exercise it’s permutations, and then refactor before you are done.” That’s critical to refactoring. You need to have the faith that there are unit tests that will exercise the code you are refactoring. If not, you’re taking chances. Ensure the code is tested before you refactor it.
Remember what really matters
I was once part of a development team where the manager declared that the most important thing was that we deliver the software on time. There’s a nuance and perspective that I like to add to this point of view. I agree, never lose sight of who is paying the bills. After all, the first order of business must be to stay in business. You have to deliver and keep your promises. To a certain extent, that means you are obliged to only promise what you can deliver. That’s how you build trust – and not just in the software development world either. Delivering on your word is the universal technique for gaining trust.
Here’s another important consideration. Most teams are comprised of a mix of junior and senior developers working together. Obviously the skills of all will and need to grow. Software development is an expensive proposition, even when ignoring failed projects. Dollars spent, time spent, it all matters. And no one like to pay what it really costs to develop custom projects.
But if all you do is deliver a software product when you are done, you haven’t done enough. You should also be delivering a better quality development team.
The product should be finished, yes. But the team should be better as a result too.
When I first heard about this I thought the rider must have been crazy to try something so risky. Last night the Iowa police pursued a motorcyclist on I-80 in Iowa with speeds reaching 188 MPH. The rider was tracked by a police airplane.
Here’s the article.