Friday, August 13, 2010
The Compiler Is Not The Audience
Is the code that you write making life easier or harder for the next person who has to work in the same area? Are you creating complexity and fragility that will slow them down, or platforms, patterns, and utilities that will speed them up?
Thursday, August 12, 2010
test modularity
Principle: keep your tests of functionality separate from your tests of business rules.
Example: I have some code that provisions licenses (bundles of permissions) to entities. I should have one suite of tests that verifies that it is possible to provision arbitrary combinations of permissions correctly - that is, the functionality. The clients of my code should have tests that verify that they are choosing to provision the particular combinations they expect - that is, the business rules. My tests should not fail when the business decision of what permissions to grant to which entities gets changed.
Example: I have some code that provisions licenses (bundles of permissions) to entities. I should have one suite of tests that verifies that it is possible to provision arbitrary combinations of permissions correctly - that is, the functionality. The clients of my code should have tests that verify that they are choosing to provision the particular combinations they expect - that is, the business rules. My tests should not fail when the business decision of what permissions to grant to which entities gets changed.
Tuesday, August 10, 2010
Code ownership
As an aside: I've not written much lately because I'm working for a non-open-source company, which limits what I can talk about without crossing IP boundaries. I've decided to start posting again but will have to be a bit vague.
My company's roots are in web application development. This seems to contribute to a more horizontal rather than structured architectural style: all groups for themselves, each working on small user-facing features. There is a general principle that everyone is allowed to check code into anyone's area: code "ownership" is discouraged.
I find this a problem. At this point our codebase is quite large. No one, not even the principals of the company, understand it all; I routinely ask them questions and get back answers like "well, it's been a while since I worked on that so I'm not sure." But at the same time, no one can fully understand even the piece they work on, because it has been partied on by a myriad of developers, few of whom were well versed in its design, its test suite, its intentions, its history.
My company's roots are in web application development. This seems to contribute to a more horizontal rather than structured architectural style: all groups for themselves, each working on small user-facing features. There is a general principle that everyone is allowed to check code into anyone's area: code "ownership" is discouraged.
I find this a problem. At this point our codebase is quite large. No one, not even the principals of the company, understand it all; I routinely ask them questions and get back answers like "well, it's been a while since I worked on that so I'm not sure." But at the same time, no one can fully understand even the piece they work on, because it has been partied on by a myriad of developers, few of whom were well versed in its design, its test suite, its intentions, its history.
Tuesday, January 12, 2010
Dual dispatch
Computer programming is about describing the behavior of entities in various scenarios. For instance, if you're writing a data entry form, you might need to describe how it behaves when you click a certain button, how it behaves when you type into a field, and so forth. In object-oriented programming, we try to organize the code that describes an object's behavior together with the data that describes its state.
This gets messy when objects interact. Suppose you've got three Animals (Dog, Cat, Monkey), and three kinds of Food (DogChow, CatChow, MonkeyChow). You want your program to ensure that Monkeys can only eat Monkey Chow, Cats can only eat Cat Chow, but Dogs can eat all three kinds. So, you add a method to each of the three Animal classes, something like "boolean canEat(Food chow)". The implementation for Cat looks like "return chow instanceof CatChow", Dog looks like "return true", and so forth.
What happens when you add an animal? No problem, you just have to implement its canEat() method. What about when you add a new type of food? Well, you have to go through all the existing animals and make sure their implementation is still right. For instance, since chocolate is poisonous to dogs, the Dog implementation of canEat() is wrong. No compiler error, but your dog might die.
Or, you could flip the problem around, and put methods on all the Foods, saying what animals can eat it. Now, when you add a food, it's just a matter of implementing its canBeEatenBy(Animal animal) method. But when you add a new animal, you have to check all the Food implementations.
This problem of how to describe the interactions between entities is called "dual dispatch." I know of no particularly good general solution.
This gets messy when objects interact. Suppose you've got three Animals (Dog, Cat, Monkey), and three kinds of Food (DogChow, CatChow, MonkeyChow). You want your program to ensure that Monkeys can only eat Monkey Chow, Cats can only eat Cat Chow, but Dogs can eat all three kinds. So, you add a method to each of the three Animal classes, something like "boolean canEat(Food chow)". The implementation for Cat looks like "return chow instanceof CatChow", Dog looks like "return true", and so forth.
What happens when you add an animal? No problem, you just have to implement its canEat() method. What about when you add a new type of food? Well, you have to go through all the existing animals and make sure their implementation is still right. For instance, since chocolate is poisonous to dogs, the Dog implementation of canEat() is wrong. No compiler error, but your dog might die.
Or, you could flip the problem around, and put methods on all the Foods, saying what animals can eat it. Now, when you add a food, it's just a matter of implementing its canBeEatenBy(Animal animal) method. But when you add a new animal, you have to check all the Food implementations.
This problem of how to describe the interactions between entities is called "dual dispatch." I know of no particularly good general solution.
Thursday, September 3, 2009
Test Automation
Writing good tests is hard: test code tends to be opaque, fragile, and hard to maintain even when written by the same developers who write decent product code. Why?
Test code is inherently introspective: it is about the code it is testing. Product code is about a user-domain problem - for example, managing the relationships between a company and its customers. Test code is about the user-domain problem and about the product code. As a concrete example: writing tests often requires exposing (through package sharing or through "test-only" APIs) methods that don't make sense otherwise, in order to give the test code the ability to inspect the internal state of objects in the product code.
Introspection, or self-reference, increases conceptual complexity. An object that does something is simpler than an object that reasons about an object that does something.
Test code is inherently introspective: it is about the code it is testing. Product code is about a user-domain problem - for example, managing the relationships between a company and its customers. Test code is about the user-domain problem and about the product code. As a concrete example: writing tests often requires exposing (through package sharing or through "test-only" APIs) methods that don't make sense otherwise, in order to give the test code the ability to inspect the internal state of objects in the product code.
Introspection, or self-reference, increases conceptual complexity. An object that does something is simpler than an object that reasons about an object that does something.
Monday, July 13, 2009
Dogfood
The company where I spent my vacation has a very strong tradition of "eating one's own dogfood," which means using the products they are developing. This is supposed to improve the quality of the product, because one becomes painfully aware of the problems and is motivated to fix them.
My recollection is that when I first heard the phrase, back in the early 1990s, it was "tasting," not "eating," one's own dogfood. There is a significant difference between the two.
The problem with eating only your own dogfood is that you start getting used to the taste of dogfood, and you don't discover that the rest of the world has learned how to cook a decent meal.
Maybe it is better to eat whatever the best food around is, while occasionally being forced to taste one's own dogfood. For example, let development teams use whatever products they want, but have test days where the teams work through predefined customer use cases using their own products.
My recollection is that when I first heard the phrase, back in the early 1990s, it was "tasting," not "eating," one's own dogfood. There is a significant difference between the two.
The problem with eating only your own dogfood is that you start getting used to the taste of dogfood, and you don't discover that the rest of the world has learned how to cook a decent meal.
Maybe it is better to eat whatever the best food around is, while occasionally being forced to taste one's own dogfood. For example, let development teams use whatever products they want, but have test days where the teams work through predefined customer use cases using their own products.
Tuesday, June 30, 2009
Vacation
I spent the last month on "vacation", working for a different company on a different product in a different programming language. It was actually quite a lot of fun; I'd like to take more such vacations (not least because, unlike the sort where one travels to a different country, I got paid to do it.)
I was working primarily in C#, a language I've not spent much time in till now. I was also working on Windows-specific user interface code, rather than the platform-independent systems-level code I normally write.
C# is a weird language. It feels to me like something of a kitchen-sink language: it's got a bit of practically every language idea I've ever heard of. Generics, function delegates, event handlers, closures, ... there are about five times as many keywords as Java has. Java is to C# as Spanish is to English.
There were a few things that I really liked: I did get pretty friendly with closures, for instance, and the language support for iterators is cool. Java still feels more elegant to me, though. There's just too many ways to skin the same cat in C#. And Java's collection and concurrency libraries are still way better.
The one thing I most disliked about C# isn't really a fault of the language as such: the naming conventions. In Java, by convention, we name types with capital letters; methods, variables, and packages with lower-case. This helps disambiguate some syntax that would otherwise be hard for humans to read (even though it doesn't bother the compiler at all). In C#, by convention, namespaces, types, and methods are all capitalized; fields and variables aren't. Properties, which are fields that have built-in getter and setter methods (so they can be accessed by assignment syntax), are capitalized. To my eye, it's hard to read and ugly.
To a compiler, none of these distinctions really make much difference; the differences between the .NET and Java virtual machines is pretty small. Programming languages, as I've said before, are a way for programmers to express ideas: they're for people, not computers. Esthetics matters.
If I were in St. Louis, I'd be attending this talk on the Fan programming language, which claims to be compilable to both virtual machines and to get rid of a lot of the uglifications of both Java and C#.
I was working primarily in C#, a language I've not spent much time in till now. I was also working on Windows-specific user interface code, rather than the platform-independent systems-level code I normally write.
C# is a weird language. It feels to me like something of a kitchen-sink language: it's got a bit of practically every language idea I've ever heard of. Generics, function delegates, event handlers, closures, ... there are about five times as many keywords as Java has. Java is to C# as Spanish is to English.
There were a few things that I really liked: I did get pretty friendly with closures, for instance, and the language support for iterators is cool. Java still feels more elegant to me, though. There's just too many ways to skin the same cat in C#. And Java's collection and concurrency libraries are still way better.
The one thing I most disliked about C# isn't really a fault of the language as such: the naming conventions. In Java, by convention, we name types with capital letters; methods, variables, and packages with lower-case. This helps disambiguate some syntax that would otherwise be hard for humans to read (even though it doesn't bother the compiler at all). In C#, by convention, namespaces, types, and methods are all capitalized; fields and variables aren't. Properties, which are fields that have built-in getter and setter methods (so they can be accessed by assignment syntax), are capitalized. To my eye, it's hard to read and ugly.
To a compiler, none of these distinctions really make much difference; the differences between the .NET and Java virtual machines is pretty small. Programming languages, as I've said before, are a way for programmers to express ideas: they're for people, not computers. Esthetics matters.
If I were in St. Louis, I'd be attending this talk on the Fan programming language, which claims to be compilable to both virtual machines and to get rid of a lot of the uglifications of both Java and C#.
Subscribe to:
Posts (Atom)
