I've been following the discussions of ExtremeProgramming for 4 or 5 months. Six weeks ago, I was given responsibility for developing a portion of a customer demo. I choose to use Ada95 (over C++ or Java) and attempted to apply XP as I understood it. As I approach the end of this short project, I have some thoughts.
Several concepts worked very well. Religious refactoring was wonderful. I attempted to keep all my routines to a single page (less than 60 lines). The seasoned XP folk would probably say this is way too long, but I think that's partly an artifact of Ada's wordiness. Ada is very robust in the ways it allows you to reuse or generalize code (tagged types; package hierarchies; generic packages, functions, and procedures; etc) and this encouraged refactoring.
I believe that the average method length in C3 is under 10 lines. But that is surely a result of Smalltalk's compactness as well as practiced focus on small methods.
I attempted to first get a minimal skeleton working and tested and then incrementally add needed functionality. Sometimes I succeeded. Sometimes I was led astray trying to make large additions. In general, it seems adding functionality shouldn't break the code for more than 2 days. If it does, the added functionality should probably be broken into smaller chunks.
We try to break the code for no more than a couple of minutes, literally. In almost every case where a programmer has failed to complete changes in two days, it has been desirable/necessary to throw the code away and start over. More on this in BrokenCode.
I was fairly successful with not doing more than was needed. It's interesting how desirable it was to provide a full complement of features for certain types. With the tight time constraints and with this being a demo, I sometimes choose simple albeit inefficient solutions. These solutions I tried to annotate with comments to inform any successors on ways to improve the system. I think this might be a good general principle since performance is always a problem and you probably shouldn't sweat it until the whole system is build.
Excellent! We're glad to hear you were successful with YAGNI. How did you discipline yourself to it?
I failed in fully applying the rigorous testing. First, it's hard to not break test scenarios and test drivers as you incrementally fill out the system. Second, I incrementally understood the inputs and outputs of the system and the expected behavior. My feeling is that the testing style will differ drastically based on programming languages and types of system (client-server, data processing, user interface, database, etc). I need more enlightenment.
Testing style might differ, but I wouldn't expect it to be as difficult as you discovered. We need to do a better job of describing how to unit test and how to develop in the always-100% mode. See BrokenCode.
The requirements for my code were based heavily upon existing documents. I scoured the interface document to understand my inputs and outputs, and talked extensively with one of the engineers responsible for the proposal to determine the system behavior. I still made mistakes on interpreting its behavior. Using object-based programming greatly reduced the ripples, though. Another area which I feel needs more study.
Even the ExtremeProgrammingMaster uses a comment once in a while. The trick is not to let them substitute for writing good code. Names such as Do_This_And_Conditionally_Do_That, however, can usually be avoided. Think in terms of the intention of the method (its purpose), not its implementation. Probably the method does something that does have a single-concept behind it ... or it wouldn't belong in a single method anyway. Finish_With_Document might be the intentional name for a method that could have been called Close_Window_And_Optionally_Empty_Trash.
I have also been toying with ExtremeProgramming in C with very limited success. Is it possible XP works better with object-oriented programming than it does with structured programming? More likely, I'm just a crummy C programmer :)
I have a lot of C experience, and would enjoy going back to it with an XP orientation. I'd expect it to serve me well there. It was always my practice to limit routines to a few lines, even in C, which would help. However, refactoring is always harder in a language that can't hand you a window with all the references to the old name, for changing to the new one.
Any thoughts?
-- WayneCarson
It seems to me that strongly typed languages have a significant advantage here, because of their ability to maintain 'automated consistency'. As a long-time Ada programmer, I've learned how to use the language to do a lot of consistency checking for me; when the code compiles, it almost always runs. The remaining errors are design errors, which are exactly the kind of intellectual errors that humans should be concentrating on. Furthermore, the checking-in-the-large and separate compilation makes it easier to detect problems from reorganizing a program. One tool that I've wanted is a "hoister tool" that allows me to move, for example, a procedure from one unit to another. The "hoister tool" will resolve namespace changes, compilation order, etc, or tell me why it can't make my program consistent again.
Thus, I suspect that the 'issue' is not object-oriented, but rather strongly-typed; I'd expect Java to be "better" than C++ for this reason.
-- dave emery
Regarding C and windows with variable references. I find that I can program in an extreme manner in C++ by leveraging the compiler. Writing code using undeclared methods and changing method and variable names deliberately to force errors on recompilation. A good IDE puts you at the point of the error, which is an area that you need to touch. I know it sounds trivial, but when I look over other people's shoulders, I often don't see this technique used deliberately in compiled languages. It is often as fast as a search if your dependencies are in order, and it does not break your rhythm. -- MichaelFeathers
Neat idea, Michael. It'd surely work in Java too. Next time someone holds a gun to my head and makes me work on one of those, I'll try it! -- RonJeffries
One example would be removing all the "#includes" or "package" declarations from the start of a C/C++ or Java program, submit it to the compiler and put back in the packages it complains about. Another example would be to change the type of an instance variable from short to long, and rename it according to HungarianNotation, and recompile. All references to the old name will show up as compiler errors, and a good IDE will take you to the source code of each one in turn.
Incidentally, this is in contrast with the school of thought which says that your code should always compile correctly first time. That you shouldn't allow yourself to make mistakes, hoping that the compiler will catch them, because compilers don't catch all mistakes. Michael is suggesting we deliberately submit known bad code to the compiler so that it can help pin-point why it is bad.
You can see there's an element of risk involved - perhaps a name clash, previously hidden by scoping rules, will emerge and make one instance of bad code compile after all. This could lead to a subtle bug, hard to find by reading the source. "Machines increase the number of things we do without thinking,", which is always a mixed blessing.
I do find that this technique amplifies the MentalStateCalledFlow. In that state, I find that I am thinking not only about where I am in the code, but where I will be after the next compile for the errors that I know will occur. I can make a strategic decision to work on something else once I see what the errors are, and even mark a place in code by typing gibberish in place and hitting F5 (compile in my IDE) again. The goal is EconomyOfMotion?. Less mental state is spent on moving to the search command and more is spent on thinking several moves in advance. It helps to have a well threaded IDE which allows you to call up and edit while the compilation is in the background. In lieu of that, if the compile takes more than three seconds, you have a moment to reflect while you know that the compiler is doing tedious work that is nonetheless profitable. So, it does replace some thinking, but it allows time for other thinking. In a sense, this is PairProgramming with the computer. Sometimes juggling six eggs is easier than juggling three.
That said, I do know that there are risks. I suspect that there is plenty to learn by coding next to someone. I'm eager to know whether anyone else codes this way, or whether this can be considered hazardous. I haven't encountered the name clash bug so far. -- MichaelFeathers
I tend to over-emphasis the risks. The "WithoutThinking?" quote about the dangers of technology affected me profoundly when I first came across it, and I am rarely able to explain it to others well.
The general idea of an environment that understands the subject matter at hand, and helps you navigate through it, is clearly valuable, and the compiler+IDE combination is an available first-draft. As we've described it, it works best (I think?) with statically typed languages - you wouldn't try it with Smalltalk. However, I this is directly analogous with some of the descriptions of working with the Smalltalk runtime, where the problem spots are found by executing rather than compiling. -- DaveHarris
Because of difficulties in gaining access to standard libraries, I was forced to develop some basic data structures. I needed a dynamically-sized data container for keeping track of expected cutoff times of certain events. If the event isn't completed by its cutoff time, do some processing.
I would need to be able to get the next most recent cutoff, and delay until that time. I would need to able to insert into the container and delete from the container.
Ideally this would probably be implemented with a self-balancing tree using time to order it (this reduces the search time for insertions and deletions).
What do I already have available? I have a generic prioritized queue package and a list package. I look at the prioritized queue package. It uses an enumeration for prioritizing. It would take a lot of work to make it handle a non-discrete type. I could easily use the list package to make a queue package with insertion based on a key. I choose this path. The package was named something like Prioritized_Queue. In my interface, I provide visibility only to the routines I need now (Insert, Delete, Top_Element_Of). Since this container will exist for the lifetime of the process, I don't even include a Dispose routine. In the interface, I include a warning that this isn't efficient and should later be reimplemented.
Thinking back, I broke the rule a little by making this package generic even though I had need for only one prioritized queue.
I guess the important aspects were recognizing abstractly what I needed, mapping this to conventional programming concepts, and using existing building blocks to implement these concepts. YAGNI needs to be linked with mapping needs to software patterns to create simple, easily understood systems.
I also tried to look at the reasonable needs of the container before starting to implement. I didn't want to choose an implementation which would poorly scale to my actual needs.
Does this answer the question? -- WayneCarson
This page mirrored in ExtremeProgrammingRoadmap as of April 29, 2006