| Exploring Solution Spaces © Copyright 2003-2006, by C. Keith Ray | ||||||||||||||||||||||||
|
Archives
Subscribe |
2003.Nov.30 Sun The XP2004 conference on Extreme Programming is scheduled for June 6-10, 2004 in Garmisch-Partenkirchen, Germany, 90 km from Munich. Trivia fact: In 1968 the legendary Nato Software Engineering Conference was held in Garmisch-Partenkirchen, where for the first time the term 'software engineering' was used. There's a call for experience papers, tutorials, workshops, panels, PhD Symposium, etc. Chris Gardner on the XP mailing list points to author Mark Forster's blog on "growing articles". He presents six drafts of an article on this subject. The first draft simply consists of the words "Growing articles". You can see the final draft in his November 28th blog entry, and all six drafts here. 2003.Nov.29 SatJames A. Robertson linked to an article in Industry Week, which describes five things unhappy companies have in common:
Check it out. 2003.Nov.27 ThuDid you know there are 50 members of Congress cosponsoring a bill to form a Department of Peace? I had to hear about it from a lecture televised on C-SPAN2 - an audience member asked Al Franken if he had heard of it. Neither of the two newspapers I read regularly have mentioned it. 2003.Nov.26 WedJohn Tarrant in the January 2004 issue of Shambala Sun (a magazine from the future!), writes: 2003.Nov.23 Sun Martin Fowler writes of the CommandOrientedInterface and command objects and executors: Command oriented interfaces have a number of benefits. One of the primary ones is the ability to easily add common behavior to commands by decorating the command executor. This is very handy for handling transactions, logging, and the like. Commands can be queued for later execution and [...] be passed across a network. Command results can be cached [...] And, of course, it is relatively easy to add "Undo/Redo" support to command objects and store stacks of command objects to be undone/redone. In a dynamic language that supports reflection, one could create generic command objects by creating a class that retains a reference to an arbitrary object [call it a 'target'], a reference to an arbitrary method of that object [call that an 'action'], and a list of arguments to pass into that method [or perhaps only one argument, the 'sender']. Instances of this class could be created by reading a configuration file or deserializing instances that had been created in an Interface Builder. I'm not saying that Apple's Interface Builder does exactly like this, but it is doing something kind of like this. See this page for more. Cocoa and Interface builder allow creating powerful programs without writing (or generating) a lot of code. It's been one of the most advanced development environments for at least a decade (it used to be part of NextStep), and it still is one of the most advanced development environments.
Smalltalk, Objective-C, and no doubt Python and Ruby could generic command-object classes like this very succinctly. Java would be more difficult, but it can be done in Java, as well.
Perhaps if methods and method-calls (and classes) were more easily thought of as first-class objects [which is very far from the case in C++], tools for Aspect-Oriented programming would be easier or unnecessary. 2003.Nov.18 TueI think the power of a Smalltalk environment is not just the language, and not just the environment. It's the combination of those and the Object Model - the concepts that underlay the syntax. (And a good class library helps a lot.) I'd like to use a programming environment which has a choice of syntaxes but which uses the same object model. I could enter code in any of the following syntaxes, and see existing code decompiled into any of the following syntaxes:
Interestingly, there is one environment that is approaching the nirvana of multiple-syntaxes with a common object model. Apple's Cocoa, which is implemented in Objective-C, which has an object model inspired by Smalltalk, interoperates with Python, Ruby, AppleScript, and Java. Apple's XCode/ProjectBuilder environment allows writing code in these scripting languages and C/Objective-C/C++/Java/etc. There are a lot of good Cocoa books out there. My favorite so far is by Aaron Hillegass: Cocoa Programming for Mac OS X It's not a lengthy as the others, and it covers a lot of what makes up a Cocoa application. 2003.Nov.16 SunAfter my wife read the book A Housekeeper Is Cheaper Than a Divorce, we hired a maid-service to clean our place once a week. Marina has been reliable and helpful. Now she has her own website: http://www.myhousecleaner.com/.
Innovator's Dilemma and Solution
I've read The Innovator's Dilemma and I'm reading The Innovator's Solution. I recommend reading these books. The authors main points are that (1) established companies almost always get "trapped" by their customers into only supporting "sustaining" innovations - incremental improvements in their products or services, and (2) only (for the most part) new companies are able to take advantage of "disruptive" innovations that create new markets and eventually take over established markets "from below". For example, in the steel-mill arena, Minimills using a new, cheaper, process were at first only able to sell into the lowest-quality product area: rebar. The established companies stopped producing rebar because of the low margins, and concentrating on satisfying their most "demanding" customers in profitable high-quality product areas. But the minimills continued to improve their quality, and eventually took over more and more product areas - always coming in from below, always taking lower-margin business away from established players. At any time, the established traditional steel mill companies could have bought one of the existing minimill companies, or established their own minimills, but the lower margins are too unattractive for people used to higher margins. It seems likely that minimills will eventually take over the entire steel industry, and the people used to high margins will have to get used to no margins at all. Disruptive Innovation can also create new markets where none were before. The established companies, particularly large ones, do not have an incentive to sell into a new market, because the new market is so small compared to their existing markets. Sales would be 1/100th or smaller in comparison, and not attractive to people used to making larger sales numbers. Apple experienced this with the Newton. It pretty much established a new market and sold more Newton MessagePads in two years than they had sold Apple IIs in its first two years. The difference is that Apple was a much smaller company when it was selling Apple IIs (and had invested less money in Apple IIs than it had invested in Newtons.) A small company (like Palm originally was) would spend less on the product's development, and be more satisfied with sales totals that a large company would find to be too small. 2003.Nov.15 SatOne difference between Pokayoke in manufacturing compared to software, is that software is always unique and fresh (if it isn't, some re-use is lacking). To "manufacture" a software product, simply copy the CD-ROM or post the installer on a web-site. However, pokayoke is always about quality assurance in either domain; either detecting problems as soon as possible, or preventing problems from happening. Harry Robinson has an article about a Pokayoke software tool here: http://www.campbell.berry.edu/faculty/jgrout/pokasoft.html, along with some background material about Pokayoke. Quotes:
Let me rant about bit a the Standard C Library. This library represents not the best or most appropriate set of functions that C programmers need to use, just the the ones that were most commonly implemented by C compiler vendors when the standardization process was begun. The string functions provided by the library are dangerous. For example, the string-copy function "strcpy(destBuffer,srcBuffer)" doesn't check the size of the destination buffer, so copying a too-large string can overwrite the stack or memory, provided us with countless bugs and security holes. Also countless lines of duplicate code where the programmer checked the size of the source string against the destination buffer-size before doing the copy. The Standard C Library contains what might be considered a safer string-copy function "strncpy(destBuffer,srcBuffer,destBufferSize)" that will not copy characters beyond last byte of the destination buffer. But it will not terminate the string with a null-byte in the destination buffer if the source string is longer than the destination buffer. This lack of string termination means that functions expecting a well-formed string in that destination buffer will misbehave: accessing memory beyond the end of the buffer. A programmer who does not know this "gotcha" will have created another bug and/or security defect. Once again we will have countless amounts of duplicated code as the some programmers either write a "safe strncpy" function, or add a line of code to insure the destination buffer has a terminating null-byte every time they use strncpy. And guess what? There's another problem with strncpy (and most "safe strncpy" implementations). It doesn't copy characters, it copies bytes. In multiple-byte encodings like SJIS and UTF8, half of a multiple-byte character could be left at the end of the buffer, if the buffer just happens to be one byte too short. I avoid most of the problems with strcpy/strncpy, etc., by using the C++ std::string class. It dynamically allocates memory for strings, so I never have to consider buffer-sizes. This helps mistake-proof string coding. One frustration I have with this class, is that it is possible to pass a null-pointer into the constructor. The standard says that a character-pointer passed into the constructor must not be a null-pointer, but the implementations I use will not detect this situation. Generally, if this happens, there will be a crash sooner or later, and some debugging will be required to find out how a null-pointer got passed into a std::string. If the implementation of std::string had a simple line of code -- "assert(ptr != NULL);" -- the debugging time would be reduced. C and C++ have a problem where its static type-checking falls down: integers are freely (and silently) convertible to pointers and bools, and vice-versa. In this domain, the language is "loosely-typed". The bool value 'false' can be passed into a std::string constructor because the compiler changes it character-pointer (whose value is the null-pointer). A character-pointer can be assigned to a bool variable - if the character-pointer is the null-pointer, then the bool variable takes on the value 'false', otherwise it takes on the value 'true'. Microsoft's CString class used in MFC has a "type-cast" operator method that returns a character-pointer to the CString's internal buffer. I recently found some code that "assigned" a CStdString (a cross-platform class with an API similar to CString) to a bool variable -- this silently called the character-pointer type-cast operator and assigned 'true' to that variable, since CStdString allocates its own non-null buffer. Not at all what I wanted. Fortunately, I can avoid std::string and CString and use (and customize) CStdString. My version of CStdString has an assertion to detect being passed a null-pointer. My version of CStdString does not define a character-pointer type-cast operator. These two changes help mistake-proof my code. 2003.Nov.13 ThuPokayoke is a way to prevent mistakes from being made, or to detect a mistake immediately. It is "fool-proofing". You might think that explicit/static typing is all we need for fool-proofing, but we know from experience that the presence of explicit typing does not eliminate bugs. Let's show this with an example in Java. Requirement: "The game displays the winning player's name in the game-log."
class Player
{
public int score;
public string name;
};
class GameLog
{
public string logText;
public void appendText( string textIn )
{
// implementation left for the reader.
}
};
class Game
{
public void displayWinningPlayer( Player playerOne, Player playerTwo, GameLog log )
{
}
}
Now the above code satisfies the compiler's explicit type-checking, but it doesn't satisfy the requirement. Let's build a way of detecting this failure to meet the requirements - using Test Driven Development.
// ignore other JUnit scaffolding... just show the test...
public void TestDisplayWinningPlayer() throws Exception
{
Player one;
one.score = 12;
one.name = "stan";
Player two;
two.score = 13;
two.name = "kyle";
GameLog log;
Game game;
game.displayWinningPlayer( one, two, log );
assertEquals( "kyle won with 13 points", log.text );
game.displayWinningPlayer( two, one, log );
assertEquals( "kyle won with 13 points", log.text );
}
So how do we know the test is a valid one for the requirements? Well, it looks good to me. I ask my pair partner; he thinks it looks good, too. Since we're doing Extreme Programming, we ask our on-site customer what the log should look like. She looks at our test and says "This displayed text should should have an exclamation mark at the end." So we add "!" to the end of the strings in our tests. We also validate the test by running it now, even though the displayWinningPlayer function is empty. The test fails (as we expected) because the necessary code to make it pass hasn't been written yet. If it had passed, then we would know something is wrong with our test.
assertion failure: expected 'kyle won with 13 points!' is not equal to actual ''. (line number and stack crawl follows.) Now we write just enough code to pass the tests. Unlike some examples in the TDD literature, I'm going to write the 'right' code directly, instead of taking tiny steps.
class Game
{
public void displayWinningPlayer( Player playerOne, Player playerTwo, GameLog log )
{
if ( playerOne.score > playerTwo.score )
{
log.appendText( playerOne.name + " won with " + playerOne.score + " points!" );
}
else if ( playerTwo.score > playerOne.score )
{
log.appendText( playerTwo.name + " won with " + playerOne.score + " points!" );
}
}
}
We run the test and get this output: assertion failure: expected 'kyle won with 13 points!' is not equal to actual 'kyle won with 12 points!'. (line number and stack crawl follows.) Whoops. Our second assertion failed. We can immediately see that the failure has something to do the points, since it got the name right. We don't even have to get into the debugger. Doh! Look at the source: when player-two has the higher score, we still append player one's score. We fix it, and the now the test passes. Pokayoke. We ask the customer about ties, and he says that it can't happen according to the rules of the game. I would probably document that kind of 'negative requirement' with an assertion in the code. (The assertion has no effect in 'release builds' but it does have an effect in 'debug builds' that we use in test-driven development and manual pre-release testing).
public void displayWinningPlayer( Player playerOne, Player playerTwo, GameLog log )
{
assert playerOne.score != playerTwo.score : "ties are not supposed to happen - fix your test/code";
if ( playerOne.score > playerTwo.score )
{
log.appendText( playerOne.name + " won with " + playerOne.score + " points!" );
}
else if ( playerTwo.score > playerOne.score )
{
log.appendText( playerTwo.name + " won with " + playerTwo.score + " points!" );
}
}
And one last point. I could have written all of the above in Python, which does not have explicit/static typing. The tests would still detect my failure to meet the requirement, and the code would be a lot shorter and more succinct. Extreme Programming has another way of detecting errors relatively quickly: "Frequence Releases." If possible, some end-users of this game will see versions of the game perhaps as often as every iteration. But that goes beyond pokayoke into the general area of feedback. 2003.Nov.11 TueRon Jeffries wrote something quotable today: Calling something you don't understand and don't know how to do "religion" is an entirely specious argument. There are people all around you doing these things and having them work. Tell us what happened when you tried it. Until then, it seems fatuous to tell us that we can't be doing what we are doing. 2003.Nov.08 SatSomeone wrote to Bruce Eckel asking for help:
The first thing wrong is using C++. I've used C++ for close to 16 years, but for the purposes he wants (persistence, extending a system without modifying code), it is the wrong tool. This is a case where you need a dynamic language with good reflection capabilities. Python or Smalltalk (or even Objective C or Java in some circumstances) would be better choices. The second thing possibly wrong is very likely using the relational database. Unless interoperability or scalability issues require it, it would probably be better to use persistence/serialization options available in Python or Smalltalk instead of trying to get around the relational-object impedance mismatch. The third thing, which Bruce goes into, is premature framework building. What is the problem being solved? Maybe it doesn't need a full-blown, extensible framework. The writer also complained about Visual Basic applications being too rigid to re-use. There is another tool can help for this problem: separate the GUI from the model by incrementally implementing the model test-first (Test Driven Development) and implementing thin GUI code afterwards.
Principles are Constant, Practices Can Vary
Curtis Cooley writes (book links are my addition): Womack and Jones opened my eyes in Lean Thinking to the fact that practices don't map from project to project. They discovered after their landmark book The Machine That Changed The World that manufacturing companies were having little or no success implementing Lean Manufacturing even though the company embraced and believed it. The problem was that the companies were trying to map the practices that Toyota used to their particular manufacturing process. This doesn't work. The principles of lean manufacturing are constant, so you take the principles and adopt your own practices, possibly from a list which includes practices known to work. The interesting thing is that Toyota's practices don't even stay the same from month to month. Their continual search for improvement (driven by all levels of employees - not just management or a few driven people), means trying things and fixing things all the time. See what Joe Ely has written here: Several years ago, I read a discussion of Toyota's openness in conducting tour after tour of their facility. The reason is that most visitors see things as they are. They take a "mental photograph", if you will, of a static system, resolving to try to do what they see. But the real power in Toyota's system is in its dynamic nature. It is impossible for the outsider to see just how rapidly and repeatedly the Toyota folks change the system. It is anything but static...it is a living, breathing, dynamic system that both changes constantly and is amazingly stable. PS: We as programmers will be much better off if we could tune and periodically modify not only our practices, but the IDE and programming language we use as well. (Toyota can refine and build new tools for making cars, we should make it easy for us to do refine and build new tools for making software solutions.) Smalltalk is one particularly malleable system. In Squeak and many other implementations of Smalltalk, the compiler is written in Smalltalk and is something you can modify (as well as the IDE). [Unlike for complicated languages like C++, a Smalltalk compiler is succinct and relatively more understandable.] I don't have it handy right now, but some time ago I was given a piece of code, less than third of a page in size, that adds an "increment assignment" operator to the language, so you can write something like "a :+= b" instead of "a := a + b". Many other things that would be additions to the language in other languages, are just new methods or classes in Smalltalk. 2003.Nov.07 FriThe days of the week - (mostly) forgotten gods:
See also http://www.crowl.org/Lawrence/time/days.html. 2003.Nov.06 Thu
Cost of Low Quality
Nynke Fokma's Are We Solving the Real Problem?: The global organization had a list of 5 company values for all thousands of employees all over the world. One read "Exceed Customer Expectation." How many projects had we shipped in the last year that did not live up to customer expectation, let alone "exceed" it? I had no idea. None of us was directly in touch with customers and users so how will we know how we are doing? Elizabeth Hendrickson's You Can't Test the Wings Back On an Airplane: 2003.Nov.05 Wed
Test-Driven Design/Development
I'm about two-thirds done reading Unit Testing In Java: How tests drive the code by Johannes Link. It's a very good book. Go buy it now, even if you're not programming in Java, and then finish reading my blog. :-) The summary form of test-driven development is:
So what if we don't know what we want the program to do? Writing a test can help us figure that out. Since the code hasn't been done yet, the test is "black-box" and does not necessarily commit us to any particular way of implementing the solution. We can write "Exploratory Tests" to investigate existing code - does calling "X" do "Y"? Write a test and find out. I don't have time to write lots of tests. Well, you only have to test code that you want to work. If you don't test it before you write, you'll be testing it in some fashion after you write it. Or someone else will test it, file a bug report because you didn't test it, and then you have to not only interrupt whatever you're doing and fix it, but also test it to confirm that you fixed it. And the time-delays between your writing the code, the tester finding the bug, and your trying to fix the bug means that you will be less familiar with the code, and thus fixing it will be that much harder and slower. There is, of course LOTS of code out there that was not written this way (some of it is mine). Just browse some of the code on the internet or in your company's source-code-control system - probably most of it isn't written using test-driven techniques. You'll probably find these things in non-test-driven code (particularly in projects that don't do code-reviews or pair-programming and refactoring):
There is in fact a whole list of "code smells" (possible design problems) at http://c2.com/cgi/wiki?CodeSmell. TDD helps prevent some of them, and refactoring with the safety net of tests can allow you to to fix the others. With TDD and Refactoring, you don't have to live with smelly code. I find that once my tests are passing, I almost never have to go back and fix any bugs later. This saves me a lot of time. At the worse, it takes about the same time as coding + debugging, but it's less stressful. If a bug arises during TDD, it is most likely in code that I wrote less than five minutes ago -- easily found and fixed. For those people who say they don't have time to write tests: why do you have time to write dead code, duplicated code, and where does the time to fix bugs come from? In traditional development and testing, you create a lot of defect-ridden code, and then test the defects out. The underlying assumption is that this is the only way it can be done. Test-Driven-Design works with a different assumption: You can put the quality in first, and not have to spend time getting defects out. Traditional (maintenance) development, since it usually isn't supported by a suite of tests, eventually grinds to a halt: the code becomes so fragile that it can't be modified without breaking something. I don't know if the IRS has depreciation tables for source-code assets, but the reality is that code eventually loses value unless you take steps to keep it from losing value. Sometimes, code becomes unmaintainable while the company is still trying to make money off of it - the result is the company loses money or doesn't get the opportunity to modify the code to make more money. Test-Driven-Design provides a suite of tests that allows you to refactor - keeping the code maintainable so it is easy to enhance, re-use, etc., so you can make money. 2003.Nov.01 SatJoe Ely mentions a Lean Conference. While most of the sessions are aimed at Lean Manufacturing, and a few aimed at Lean Office work, I found this session description particularly interesting and applicable to Lean Software Development:
|
|||||||||||||||||||||||