A Scala-style "with" Construct for Ruby 4
Scala has a “mixin” construct called traits, which are roughly analogous to Ruby modules. They allow you to create reusable, modular bits of state and behavior and use them to compose classes and other traits or modules.
The syntax for using Scala traits is quite elegant. It’s straightforward to implement the same syntax in Ruby and doing so has a few useful advantages.
For example, here is a Scala example that uses a trait to trace calls to a Worker.work method.
// run with "scala example.scala"
class Worker {
def work() = "work"
}
trait WorkerTracer extends Worker {
override def work() = "Before, " + super.work() + ", After"
}
val worker = new Worker with WorkerTracer
println(worker.work()) // => Before, work, After
Note that WorkerTracer extends Worker so it can override the work method. Since Scala is statically typed, you can’t just define an override method and call super unless the compiler knows there really is a “super” method!
Here’s a Ruby equivalent.
# run with "ruby example.rb"
module WorkerTracer
def work; "Before, #{super}, After"; end
end
class Worker
def work; "work"; end
end
class TracedWorker < Worker
include WorkerTracer
end
worker = TracedWorker.new
puts worker.work # => Before, work, After
Note that we have to create a subclass, which isn’t required for the Scala case (but can be done when desired).
If you know that you will always want to trace calls to work in the Ruby case, you might be tempted to dispense with the subclass and just add include WorkerTracer in Worker. Unfortunately, this won’t work. Due to the way that Ruby resolves methods, the version of work in the module will not be found before the version defined in Worker itself. Hence the subclass seems to be the only option.
However, we can work around this using metaprogramming. We can use WorkerTracer#append_features(...). What goes in the argument list? If we pass Worker, then all instances of Worker will be effected, but actually we’ll still have the problem with the method resolution rules.
If we just want to affect one object and work around the method resolution roles, then we need to pass the singleton class (or eigenclass or metaclass ...) for the object, which you can get with the following expression.
metaclass = class << worker; self; end
So, to encapsulate all this and to get back to the original goal of implementing with-style semantics, here is an implementation that adds a with method to Object, wrapped in an rspec example.
# run with "spec ruby_with_spec.rb"
require 'rubygems'
require 'spec'
# Warning, monkeypatching Object, especially with a name
# that might be commonly used is fraught with peril!!
class Object
def with *modules
metaclass = class << self; self; end
modules.flatten.each do |m|
m.send :append_features, metaclass
end
self
end
end
module WorkerTracer
def work; "Before, #{super}, After"; end
end
module WorkerTracer1
def work; "Before1, #{super}, After1"; end
end
class Worker
def work; "work"; end
end
describe "Object#with" do
it "should make no changes to an object if no modules are specified" do
worker = Worker.new.with
worker.work.should == "work"
end
it "should override any methods with a module's methods of the same name" do
worker = Worker.new.with WorkerTracer
worker.work.should == "Before, work, After"
end
it "should stack overrides for multiple modules" do
worker = Worker.new.with(WorkerTracer).with(WorkerTracer1)
worker.work.should == "Before1, Before, work, After, After1"
end
it "should stack overrides for a list of modules" do
worker = Worker.new.with WorkerTracer, WorkerTracer1
worker.work.should == "Before1, Before, work, After, After1"
end
it "should stack overrides for an array of modules" do
worker = Worker.new.with [WorkerTracer, WorkerTracer1]
worker.work.should == "Before1, Before, work, After, After1"
end
end
You should carefully consider the warning about monkeypatching Object! Also, note that Module.append_features is actually private, so I had to use m.send :append_features, ... instead.
The syntax is reasonably intuitive and it eliminates the need for an explicit subclass. You can pass a single module, or a list or array of them. Because with returns the object, you can also chain with calls.
A final note; many developers steer clear of metaprogramming and reflection features in their languages, out of fear. While prudence is definitely wise, the power of these tools can dramatically accelerate your productivity. Metaprogramming is just programming. Every developer should master it.
The Liskov Substitution Principle for "Duck-Typed" Languages 3
OCP and LSP together tell us how to organize similar vs. variant behaviors. I blogged the other day about OCP in the context of languages with open classes (i.e., dynamically-typed languages). Let’s look at the Liskov Substitution Principle (LSP).
The Liskov Substitution Principle was coined by Barbara Liskov in Data Abstraction and Hierarchy (1987).
If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2, then S is a subtype of T.
I’ve always liked the elegant simplicity, yet power, of LSP. In less formal terms, it says that if a client (program) expects objects of one type to behave in a certain way, then it’s only okay to substitute objects of another type if the same expectations are satisfied.
This is our best definition of inheritance. The well-known is-a relationship between types is not precise enough. Rather, the relationship has to be behaves-as-a, which unfortunately is more of a mouthful. Note that is-a focuses on the structural relationship, while behaves-as-a focuses on the behavioral relationship. A very useful, pre-TDD design technique called Design by Contract emerges out of LSP, but that’s another topic.
Note that there is a slight assumption that I made in the previous paragraph. I said that LSP defines inheritance. Why inheritance specifically and not substitutability, in general? Well, inheritance has been the main vehicle for substitutability for most OO languages, especially the statically-typed ones.
For example, a Java application might use a simple tracing abstraction like this.
public interface Tracing {
void trace(String message);
}
Clients might use this to trace methods calls to a log. Only classes that implement the Tracer interface can be given to these clients. For example,
public class TracerClient {
private Tracer tracer;
public TracerClient(Tracer tracer) {
this.tracer = tracer;
}
public void doWork() {
tracer.trace("in doWork():");
// ...
}
}
However, Duck Typing is another form of substitutability that is commonly seen in dynamically-typed languages, like Ruby and Python.
If it walks like a duck and quacks like a duck, it must be a duck.
Informally, duck typing says that a client can use any object you give it as long as the object implements the methods the client wants to invoke on it. Put another way, the object must respond to the messages the client wants to send to it.
The object appears to be a “duck” as far as the client is concerned.
In or example, clients only care about the trace(message) method being supported. So, we might do the following in Ruby.
class TracerClient
def initialize tracer
@tracer = tracer
end
def do_work
@tracer.trace "in do_work:"
# ...
end
end
class MyTracer
def trace message
p message
end
end
client = TracerClient.new(MyTracer.new)
No “interface” is necessary. I just need to pass an object to TracerClient.initialize that responds to the trace message. Here, I defined a class for the purpose. You could also add the trace method to another type or object.
So, LSP is still essential, in the generic sense of valid substitutability, but it doesn’t have to be inheritance based.
Is Duck Typing good or bad? It largely comes down to your view about dynamically-typed vs. statically-typed languages. I don’t want to get into that debate here! However, I’ll make a few remarks.
On the negative side, without a Tracer abstraction, you have to rely on appropriate naming of objects to convey what they do (but you should be doing that anyway). Also, it’s harder to find all the “tracing-behaving” objects in the system.
On the other hand, the client really doesn’t care about a “Tracer” type, only a single method. So, we’ve decoupled “client” and “server” just a bit more. This decoupling is more evident when using closures to express behavior, e.g., for Enumerable methods. In our case, we could write the following.
class TracerClient2
def initialize &tracer
@tracer = tracer
end
def do_work
@tracer.call "in do_work:"
# ...
end
end
client = TracerClient2.new {|message| p "block tracer: #{message}"}
For comparison, consider how we might approach substitutability in Scala. As a statically-typed language, Scala doesn’t support duck typing per se, but it does support a very similar mechanism called structural types.
Essentially, structural types let us declare that a method parameter must support one or more methods, without having to say it supports a full interface. Loosely speaking, it’s like using an anonymous interface.
In our Java example, when we declare a tracer object in our client, we would be able to declare that is supports trace, without having to specify that it implements a full interface.
To be explicit, recall our Java constructor for TestClient.
public class TracerClient {
public TracerClient(Tracer tracer) { ... }
// ...
}
}
In Scala, a complete example would be the following.
class ScalaTracerClient(val tracer: { def trace(message:String) }) {
def doWork() = { tracer.trace("doWork") }
}
class ScalaTracer() {
def trace(message: String) = { println("Scala: "+message) }
}
object TestScalaTracerClient {
def main() {
val client = new ScalaTracerClient(new ScalaTracer())
client.doWork();
}
}
TestScalaTracerClient.main()
Recall from my previous blogs on Scala, the argument list to the class name is the constructor arguments. The constructor takes a tracer argument whose “type” (after the ’:’) is { def trace(message:String) }. That is, all we require of tracer is that it support the trace method.
So, we get duck type-like behavior, but statically type checked. We’ll get a compile error, rather than a run-time error, if someone passes an object to the client that doesn’t respond to tracer.
To conclude, LSP can be reworded very slightly.
If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2, then S is substitutable for T.
I replaced a subtype of with substitutable for.
An important point is that the idea of a “contract” between the types and their clients is still important, even in a language with duck-typing or structural typing. However, languages with these features give us more ways to extend our system, while still supporting LSP.
I love the 90's: The Fusion Episode 6
A few weeks back I was working with a team on the East Coast. They wanted to develop a simulator to assist in testing other software components. Their system to simulate is well-described in a specification using diagrams close to sequence diagrams as described in the UML.
In fact, these diagrams were of a variety I’d call “system” sequence diagrams. They described the interaction between outside entities (actors – in this case another system) and the system to be simulated.
This brought be back to 1993 when I was introduced to The Fusion Method by Coleman et al. Before that I had read Booch (1 and 2) and Rumbaugh (OMT) and I honestly didn’t follow much of their material – I had book knowledge but I really didn’t practice it. I always thought that Booch was especially strong in Design ideas and notation but weak in Analysis. I though the opposite for Rumbaugh, so the two together + Jacobson with Use Cases and Business Modeling really formed a great team in terms of covering the kinds of things you need to cover in a thorough software process (UP + UML).
But before all that was Fusion.
Several colleagues and I really groked Fusion. It started with system sequence diagrams showing interactions much like the specification I mentioned above. It also described a difference between analysis and design (and if Uncle Bob reads this, he’ll probably have some strong words about so-called object-oriented analysis, well this was 15 years ago… though I still there there is some value to be found there). Anyway, this is mostly about system sequence diagrams so I won’t say much more about that excellent process.
Each system sequence diagram represented a scenario. To represent many scenarios, Fusion offered a BNF-based syntax to express those various scenarios. (I understand that this was also because for political reasons within HP they were not allowed to include a state model, but I don’t know if that is true or not.) For several years I practiced Fusion and really I often revert back to that if I’m not trying to do anything in particular.
Spending a little time up front thinking a little about the logical interaction between the system and its various actors helps me get a big picture view of the system boundary and its general flow. I have also found it helps others as well, but your mileage may vary.
So when I viewed their protocol specification, it really brought back some good memories. And in fact, that’s how we decided to look at the problem.
(What follows is highly-idealized)
We reviewed their specification and decided we’d try to work through the initialization sequence and then work through one sequence that involved “completing a simple job.” I need to keep this high level to keep the identity of the company a secret.
There was prior work and we kept that in mind but really started from scratch. In our very first attempt, there had been some work done along the lines of using the Command pattern, so we started there. Of course, once we did our first command, we backed off and when with a more basic design that seemed to fit the complexity a bit better (starting with the command pattern at the beginning is an example of solution-problemming to use a Weinberg term – and one of the reasons I’m sometimes skeptical when people start talking in patterns).
We continued working from the request coming into the system and working its way through the system. Along the way, we wrote unit tests, driven by our end goal of trying to complete a simple job and guided by the single responsibility principle. As we thought about the system, there were several logical steps:- Receive a message from the outside as some array of bytes
- Determine the “command” represented by the bytes
- Process the parameters within the command
- Issue a message to the simulator
- Create a logical response
- Format the logical response into the underlying protocol
- Send the response back
At the time, they were considering using JNI, so we spent just over a day validating that we could communicate bi-directionally, maintaining a single process space.
Along the way we moved from using hand-rolled test doubles to using JMock 2 to create mock objects. I mentioned this to friend of mine who lamented that there are several issues using a mock-based approach:- It is easy to end up with a bunch of tested objects but no fully-connected system
- Sharing setup between various mocks is difficult and often not done so there’s a lot of violation of DRY
- You have to learn a new syntax
We accepted learning a new syntax because it was deemed less painful than maintaining existing hand-rolled test doubles (though there are several reasonable solution for that, ask if you want to know what it is). There is the issue of sharing setup on mocks, but we did not have enough work yet to really notice that as a problem. However, they were at least aware of that and we briefly discussed how to share common expectation-setting (it’s well supported).
Finally, there’s the issue of not having a fully connected system. We knew this was an issue so we started by writing an integration test using JUnit. We needed to design a system that:- Could be tested up to but excluding the JNI stuff
- Could be configured to stub out JNI or use real JNI
- Was easily configurable
- Was automatically configured by C++ (since it was a C++ process that was started to get the whole system in place)
We designed that (15 minute white-board session), coded it and ended up with a few integration tests. Along the way, we built a simple factory for creating the system fully connected. That factory was used both in tests as well as by the JNI-based classes to make sure that we had a fully-connected systems when it was finally started by C++.
Near the end, we decided we wanted to demonstrate asynchronous computation, which we did using tests. I stumbled a bit but we got it done in a few hours. We demonstrated that the system receiving messages from the outside world basically queued up requests rather than making the sender wait synchronously (we demonstrated this indirectly – that might be a later blog post – let me know if you’re interested).
By the way, that was the first week. These guys were good and I had a great time.
There was still a little work to be done on the C++ side and I only had a week, so I asked them to keep me posted. The following Tuesday they had the first end-to-end interaction, system initialization.
By Wednesday (so 3 business days later), they had a complete demonstration of end-to-end interaction with a single, simple job finishing. Not long after that they demonstrated several simple jobs finishing. The next thing on their list? Completing more complex jobs, system configuration, etc.
However, it all goes back to having a well-defined protocol. After we had one system interaction described end-to-end, doing the next thing was easier:- Select a system interaction
- List all of the steps it needs to accomplish (some requests required a response, some did not)
- Write unit tests for each “arm” of the interaction
Select a set of end-to-end interactions that add value to the user of the systemThey also had an easy way to create a sprint backlog:
For each system-level interaction, enumerate all of its steps and then add implementing those steps as individual back-log items
Now some of those individual steps will end up being small (less than an hour) but some will be quite large when they start working with variable parameters and commands that need to operate at a higher priority.
But they are well on their way and I was reminded of just how much I really enjoyed using Fusion.
Contracts and Integration Tests for Component Interfaces
I am mentoring a team that is transitioning to XP, the first team in a planned, corporate-wide transition. Recently we ran into miscommunication problems about an interface we are providing to another team.
The problems didn’t surface until a “big-bang” integration right before a major release, when it was too late to fix the problem. The feature was backed out of the release, as a result.
There are several lessons to take away from this experience and a few techniques for preventing these problems in the first place.
End-to-end automated integration tests are a well-established way of catching these problems early on. The team I’m mentoring has set up its own continuous-integration (CI) server and the team is getting pretty good at writing acceptance tests using FitNesse. However, these tests only cover the components provided by the team, not the true end-to-end user stories. So, they are imperfect as both acceptance tests and integration tests. Our longer-term goal is to automate true end-to-end acceptance and integration tests, across all components and services.
In this particular case, the other team is following a waterfall-style of development, with big design up front. Therefore, my team needed to give them an interface to design against, before we were ready to actually implement the service.
There are a couple of problems with this approach. First, the two teams should really “pair” to work out the interface and behavior across their components. As I said, we’re just starting to go Agile, but my goal is to have virtual feature teams, where members of the required component teams come together as needed to implement features. This would help prevent the miscommunication of one team defining an interface and sharing it with another team through documentation, etc. Getting people to communicate face-to-face and to write code together would minimize miscommunication.
Second, defining a service interface without the implementation is risky, because it’s very likely you will miss important details. The best way to work out the details of the interface is to test drive it in some way.
This suggests another technique I want to introduce to the team. When defining an interface for external consumption, don’t just deliver the “static” interface (source files, documentation, etc.), also deliver working Mock Objects that the other team can test against. You should develop these mocks as you test drive the interface, even if you aren’t yet working on the full implementation (for schedule or other reasons).
The mocks encapsulate and enforce the behavioral contract of the interface. Design by Contract is a very effective way of thinking about interface design and implementing automated enforcement of it. Test-driven development mostly serves the same practical function, but thinking in “contractual” terms brings clarity to tests that is often missing in many of the tests I see.
Many developers already use mocks for components that don’t exist yet and find that the mocks help them design the interfaces to those components, even while the mocks are being used to test clients of the components.
Of course, there is no guarantee that the mocks faithfully represent the actual behavior, but they will minimize surprises. Whether you have mocks or not, there is no substitute for running automated integration tests on real components as soon as possible.
Observations on Test-Driving User Interfaces
Test driving user interface development has always been a challenge. Recently, I’ve worked with two projects where most of the work has been on the user-interface components.
The first project is using Adobe Flex to create a rich interface. The team decided to adopt FunFX for acceptance testing. You write your tests in Ruby, typically using Test::Unit or RSpec.
FunFX places some constraints on your Flex application. You have to define the GUI objects in MXML, the XML-based file format for Flex applications, rather than ActionScript, and you need to add ids to all elements you want to reference.[1]
These are reasonable constraints and the first constraint promotes better quality, in fact. The MXML format is more succinct (despite the XML “noise”) and declarative than ActionScript code. This is almost always true of UI code in most languages (with notable exceptions…). Declarative vs. imperative code tends to improve quality because less code means fewer bugs, less to maintain, and it frees the implementor of the declarative “language” to pick the best implementation strategies, optimizations, etc. This characteristic is typical of Functional Languages and well-designed Domain Specific Languages, as well.
I don’t think you can underestimate the benefit of writing less code. I see too many teams whose problems would diminish considerably if they just got rid of duplication and learned to be concise.
The second project is a wiki-based application written in Java. To make deployment as simple as possible, the implementors avoided the Servlet API (no need to install Tomcat, etc.) and rolled their own web server and page rendering components. (I’m not sure I would have made these decisions myself, but I don’t think they are bad, either…)
The rendering components are object-oriented and use a number of design patterns, such as page factories with builder objects that reflect the “widgets” in the UI, HTML tags, etc. This approach makes the UI very testable with JUnit and FitNesse. In fact, the development process was a model of test-driven development.
However, the final result is flawed! It is much too difficult to change the look and feel of the application, which is essential for most UI’s, especially web UI’s. The project made the wrong tradeoffs; the design choices met the requirements of TDD very well, but they made maintenance and enhancement expensive and tedious. The application is now several years old and it has become dated, because of the expense of “refreshing” the look and feel.
What should have been done? These days, most dynamic web UI’s are built with templating engines, of which there are many in the most common programming languages. Pages defined in a templating engine are very declarative, except for the special tags where behavior is inserted. The pages are easy to change. It is mostly obvious where a particular visual element is generated, since most of the “tags” in the template look exactly like the tags in the rendered page. “Declarative” templates, like good DSL’s, can be read, understood, and even edited by the stakeholders, in this case the graphical designers.
But how do you test these page templates? When test-driving UI’s it is important to decide what to test and what not to test. The general rule for TDD is to test anything that can break. The corollary, especially relevant for UI’s, is don’t test anything when you don’t care if it changes.
It is usually the dynamic behavior of the UI that can break and should be tested. Templating engines provide special tags for inserting dynamic behavior in the underlying language (Java, Ruby, etc.). This is what you should test. It is usually best to keep the scripts in these tags as small as possible; the scripts just delegate to code, which can be test-driven in the usual way.
I see too many UI tests that compare long strings of HTML. These tests break whenever someone makes a minor look and feel or other inconsequential change. Part of the art of UI TDD is knowing how to test just what can break and nothing more. In the second project, incidental changes to the UI break tests that should be agnostic to such changes.
To conclude, keep your UI’s as declarative as you can. Only test the “declarations” (e.g., templates) in areas where they might break, meaning if it changes, it’s a bug. You’ll get the full benefits of TDD and the freedom to change the UI easily and frequently, as needed.
1 Disclaimer: my information on FunFX is second hand, so I might not have the details exactly correct; see the FunFX documentation for details.
What We Can Learn from the Ojibwe Language 3
Ojibwe (sometimes spelled Ojibwa; the last syllable is pronounced “way”) is one of the few Native American languages that isn’t immediately threatened with extinction. It is spoken by about 10,000 people around the Great Lakes region. Brothers David and Anton Treuer are helping to keep it alive, as they discussed in a recent Fresh Air interview.
Ojibwe is a language that is optimized for an aboriginal people whose lives and livelihoods depend on an intimate awareness of their environment, especially the weather and water conditions. They have many nouns and verbs for fine gradations of rain, snow, ice conditions, the way water waves look and sound, etc. You would want this clarity of detail if you ventured out on a lake every day to fish for dinner.
In the past, speaking languages like Ojibwe was actively suppressed by the government, in an attempt to assimilate Native Americans. Today, the threat of extinction is more from the sheer ubiquity of English. I think there is another force at play, too. People living a modern, so-called “developed” lifestyle just don’t need to be so aware of their environment anymore. In fact, most of us are pretty “tone deaf” to the nuances of weather and water, which is sad in a way. We just don’t perceive the need for the richness of an Ojibwe to communicate what’s important to us, like sports trivia and fashion tips.
So, what does Ojibwe have to do with programming languages? Our language choices inform the way we frame problem solving and design. I was reminded of this recently while reading Ted Neward’s series of articles on Scala. Scala is a JVM language that provides first-class support for functional programming and object-oriented design refinements like traits, which provide mixin behavior.
While you can write Java-like code in Scala, Neward demonstrates how exploiting Scala features can result in very different code for many problems. The Scala examples are simpler, but sometimes that simplicity only becomes apparent after you grasp the underlying design principle in use, like closures or functional idioms.
One of the best pieces of advice in the Pragmatic Programmer is to learn a new language every year. You should pick a language that is very different from what you know already, not one that is fundamentally similar. Even if you won’t use that language in your professional work, understanding its principles, patterns, and idioms will inform your work in whatever languages you actually use.
For example, there is a lot of fretting these days about concurrent programming, given the rise of multi-core CPUs and multiprocessor computers. We know how to write concurrent programs in our most popular imperative languages, like Java and C++, but that knowledge is somewhat specialized and not widely known in the community. This is the main reason that functional programming is suddenly interesting again. It is inherently easier to write concurrent applications using side-effect-free code. I expect that we will fail to meet the concurrency challenge if we rely exclusively on the mechanisms in our imperative languages.
So, you could adopt a functional language for all or part of your concurrent application. Or, if you can’t use Scala (or Haskell or Erlang or …) you could at least apply functional idioms, like side-effect-free functions, avoidance of mutable objects, etc. in your current imperative language. However, even that won’t be an option unless you understand those principles in the first place.
Learning a new language is more than learning a new vocabulary. It’s even more than learning new design techniques. It’s also learning to see common things from a fresh perspective, with greater clarity.
The Post-it® Notes Test for UML Diagrams 8
A lot of teams require their developers to document their designs in UML, using Visio or another tool, before they can start coding.
Of course, this is not at all Agile. For one thing, the design is likely to change quite a bit as you learn while coding. Hardly anyone returns to the diagrams and updates them. Now they are lies, because they make claims about the designs that aren’t true.
UML still has a place in agile projects, of course. It’s a great tool for brainstorming design ideas. So, how do you decide when a diagram is worth keeping and therefore, worth maintaining? Here’s a little strategy that I recommend.
Draw the diagram during those brainstorming sessions on a white board or a poster-sized Post-it® Note. Drawing it this way means you have invested almost no additional effort, beyond the brainstorming itself, to create the diagram. Also, you won’t feel bad about lost work if you eventually throw it away.
Leave the diagram on the wall for everyone to see while they implement the design.
By the time the note is falling off the wall or the dry-erase marker is wearing off the white board, you’ll know if the ideas are still relevant or completely obsolete.
If they are obsolete, you can erase the board or toss the paper. If they are still relevant, and probably changed somewhat, you now know that the diagram is worth preserving. Go ahead and spend the time to create an updated, more permanent version in your drawing tool (but don’t spend too much time!).
You Don't Know What You Don't Know Until You Take the Next Step 2
I was teaching our brand new Principles, Patterns, and Practices course recently (http://objectmentor.com/omTraining/course_ood_java_programmers.html) and I was starting the section on The Single Responsibility Principle.
I had this UML class diagram projected on the screen:
Employee
+ calculatePay()
+ save()
+ printReport()
I asked the class, “How many responsibilities does this class have?” Those students who had the courage to answer a question out loud (sadly, a rare subset of students) all mumbled, “Three.” I guess that makes sense, three methods means three responsibilities. My friend and colleague Dean Wampler would call this a first-order answer (he’s a physicist and that’s how he talks ;-) ). The number will increase as we dig into details. I held one finger in the air and said, “It knows the business rules for how to calculate its pay.” I put up a second finger and said, “It knows how to save its fields to some persistence store.”
“I happen to know that you folks use JDBC to talk to your Oracle database.” Another finger for, “It knows the SQL to save its data.” Another finger for, “It knows how to establish a database connection.” My entire hand is held up for, “It knows how to map its fields to SQLStatement parameters.” I start working on my other hand with, “It knows the content of the report.” Another finger for, “It knows the formatting of the report.” If this example was a real class from this company’s code base I knew I’d be taking off my shoes and socks.
Not that my answer was any better than the students’ answer, given the information at hand there can’t be a right answer because I didn’t provide any context for my question. I found our different answers interesting though. This particular company would have a design phase for a project where UML diagrams would be produced and discussed. How can any reviewer know if a class diagram is “rightâ€?
I have this belief (hang-up?) that you don’t really know what you don’t know, until you take the next step and actually use what you currently have. Looking at UML, we can’t really say that it’s going to work or that it satisfies the Principles of Object Oriented Design, until we take the next step, i.e., write some code and see whether it works or not. The devil is in the details and those devilish dirty details just can’t be seen in a picture.
Let’s take a step back. Before there is UML specifying a design, there must have been requirements stating a problem that the design addresses. Have we captured all of the requirements? Are they complete? Are they accurate? Are they unambiguous? How do you know? I believe that you don’t know, and worse, you don’t even know what you don’t know. You don’t know until you take that next step and try to design a solution. It’s only during design that phrases like, “What’s supposed to happen here,” or, “That doesn’t seem to have been addressed in the spec,” are heard. You don’t know what you don’t know until you take that next step.
It is very easy for everybody on a project to believe that they are doing good work and that the project is going according to plan. If we don’t know what we don’t know, it’s hard to know if we’re on the right track. During requirements gathering, business analysts can crank out user stories, use cases, functional requirements, or whatever artifacts the process du jour dictates they produce. Meetings can be scheduled and documents can be approved once every last detail has been captured, discussed to death, and revised to everybody’s liking. Unfortunately, until solutions for these requirements are designed, they are but a dream. There is no way to predict how long implementation will take so project plans are really interpretations of dreams.
The same danger exists during design. Architects can be cranking out UML class diagrams, sequence diagrams, and state transition diagrams. Abstractions are captured, Design Patterns are applied, and the size of the project documentation archive grows. Good work must be happening. But are the abstractions “rightâ€? Can the code be made to do what our diagrams require? Is the design flexible, maintainable, extensible, testable (add a few more of your favorite -able’s)? You just don’t know.
The problem with Waterfall, or at least the problem with the way most companies implement it, is that there either isn’t a feedback mechanism or the feedback loop is way too long. Projects are divided into phases and people feel that they aren’t allowed to take that crucial next step because the next step isn’t scheduled until next quarter on somebody’s PERT chart. If you don’t know what you don’t know until you use what you currently have, and the process doesn’t allow you to take the next step (more likely somebody’s interpretation of the process doesn’t let you take the next step), then we don’t have a very efficient process in place.
In order to not delude myself that I am on the right track when in reality I’m heading down a blind alley, I would like to know the error of my ways as quickly as possible. Rapid feedback is a characteristic of all of the Agile methodologies. By learning what’s missing or wrong with what I’m currently doing, I can make corrections before too much time is wasted going in the wrong direction. A short feedback loop minimizes the amount of work that I must throw away and do again.
I’m currently working with a client who wants to adopt Extreme Programming (XP) as their development methodology. What makes this difficult is that they are enhancing legacy code and the project members are geographically distributed. The legacy code aspect means that we have to figure out how we’re going to write tests and hook them into the existing code. The fact that we’re not all sitting in the same room means that we have to have more written documentation. We don’t know the nature of the test points in the system, nor do we know what to document and how much detail to provide in documentation. We can’t rely mostly on verbal communication but we don’t want to go back to writing detailed functional specs either. There are many unknowns and what makes this relevant to this discussion is, we don’t even know all of the unknowns. Rapid feedback has to be our modus operandi.
An XP project is driven by User Stories developed by the Customer Team, composed of Business Analysts and QA people. A User Story, by definition, must be sized so that the developers can complete it within an iteration. I have a sense how much work that I personally can do in a two week iteration, but I’m not the one doing the work and I don’t know how much of an obstacle the existing code is going to be. The Customer Team could go off and blindly write stories, but that would lead to a high probability that we’d have to rewrite, rework, split, and join once the developers saw the stories and gave us their estimates. To minimize the amount of rework, I suggested that the Customer Team write about twenty or so stories and then meet with the developers to go over the stories and allow them to give estimates.
My plan to get a feedback loop going on user story development worked quite well. The Customer Team actually erred on the side of stories that were too small. The developers wanted to put estimates of 0.5 on some of the stories (I have my teams estimate on a scale of 1-5. I’ll write up a blog entry on the estimation process I’ve been using.) so we combined a few of them and rewrote others to tackle a larger scope. We took the next step in our process, learned what we didn’t know, took corrective action, and moved forward.
Writing Customer Acceptance Tests didn’t go quite as smoothly, but it is yet another example of learning what didn’t work and making corrections. I advise my clients to write tests for their business logic and to specifically not write business tests through the user interface. Well guess where a lot of business logic resided – yep, in the user interface. We ran into the situation where the acceptance tests passed, but when the application was actually used through the user interface, it failed to satisfy the requirements. I’m very happy that we had a short feedback loop in place that revealed that our tests weren’t really testing anything interesting before the Customer Team had written too many FitNesse tests and the Development Team had written too many test fixtures.
Feedback is good. Rapid feedback is better. Seek it whenever you can. Learn what you don’t know, make corrections, and proceed.
"Big Balls of Mud" and Shanty Towns 2
Last Thursday, the last day of OOPSLA 2007, Brian Foote gave a restrospective of Big Ball of Mud, which he and Joseph Yoder presented at the Fourth Conference on Patterns Languages of Programs (PLoP ‘97/EuroPLoP ‘97) and which was published as a paper in 1999.
Foote and Yoder argue that the dominant architecture of deployed application is a Big Ball of Mud, which they define thusly:
A Big Ball of Mud is a haphazardly structured, sprawling, sloppy, duct-tape-and-baling-wire, spaghetti-code jungle. These systems show unmistakable signs of unregulated growth, and repeated, expedient repair. Information is shared promiscuously among distant elements of the system, often to the point where nearly all the important information becomes global or duplicated. The overall structure of the system may never have been well defined. If it was, it may have eroded beyond recognition. Programmers with a shred of architectural sensibility shun these quagmires. Only those who are unconcerned about architecture, and, perhaps, are comfortable with the inertia of the day-to-day chore of patching the holes in these failing dikes, are content to work on such systems.
I had to leave mid-way through the talk to catch a plane, but before I left, he said something that caught my attention. He compared such applications to shanty towns, those ad hoc communities that spring up with no planning, no infrastructure, and reflect the bare minimum of resources and expertise available to their builders and inhabitants.
However, as I looked at his picture of a typical shanty town, I noticed that there are paths through the maze of ad hoc homes. There is some structure there. Then it occurred to me that, for all their problems, there is one interesting difference between typical shanty towns and many software applications; shanty towns are subject to frequent “testing” and “refactoring”. Within the extreme limitations of their architectures and the available resources, the inhabitants do what they can to fix “bugs” and adapt to new “requirements”.
Of course, I’m not saying that shanty towns are good. I’m just pointing out that they have a feedback loop that leads to modest improvements. In contrast, although we application developers have more resources at our disposal, we don’t subject our applications to the same scrutiny.
Why is this? A big part of the problem is that we forget just how complex software really is. How many points of variation exist within a home and its community? How many points of variation exist within a software application? Perhaps more than one per line of (uncommented) code?
We interact with the points of variation in our homes on a regular basis, directly or indirectly, and we make adjustments as needed (unless we’re lazy ;). In contrast, most of the corresponding points in software applications are deeply hidden and not evident when we use the applications.
You know where this is going; automated testing is the only way to subject the points of variation in applications to the same level of scrutiny and to find what needs fixing.
ANN: OOPSLA Tutorial on "Principles of Aspect-Oriented Design in Java and AspectJ"
I’m doing a tutorial on aspect-oriented design principles with examples in Java and AspectJ at OOPSLA this year (October 21st). You can find a description here. I believe Friday, 9/14, is the last day for early, discounted registration, so sign up now!
A short presentation on the same subject can be found here.
Older posts: 1 2
