Tuesday, July 17, 2007 

Get the ball rolling, part 3 (of 4, pretty sure)

In the last few months I've been repeating a mantra (also in this blog): design is always highly contextual.
So, before taking a look at what I did, it's important to consider why I did it. It's also important to conside how I did it, as to put everything in perspective.

Why
- I use models quite often, but I'm not a visual modeling zealot. I know, from experience, that visual modeling can be extremely valuable in some cases, and totally useless in others (more on this next time). I can usually tell when visual modeling can be useful, using a mixture of experience, intuition, and probably some systematic rules I never took the time to write down.
When I first read the XP episode, I've been negatively impressed by the final results, and intuitively felt that I could get something better by not rushing into coding. So a first reason was to prove that point (to myself first :-).

- I aimed to remove all the bad smells I've identified in their code, without introducing others. I also wanted to prove a point: that a careful designed solution didn't have to be a code monster, as implied elsewhere. I wanted the final result to have basically the same number of rows as they had. Maybe less. Therefore, I wrote the code in a line-conscious way.

- I wanted the comparison to be fair. Therefore, I did not aim for the super-duper, ultimate bowling framework. I just aimed for a better design and better code, through a different process. I'll talk a little about what it would take to turn my design into a true "bowling framework" in my next post.

- Actually, I found the idea of not writing the ultimate bowling framework extremely useful. There has been a lot of babbling about Real Options in software development in the last few years, and I even mention a few concepts in my project management course.
However, I lacked a simple example where I could explain a few concepts better. By (consciously :-) under-engineering some parts of the design, I got just the example I needed (more on this next time).

- I also wanted to check how many bugs I could possibly put in my code without using TDD. I would just write the code, run all their tests, and see how many bugs I had to fix before I got a clean run.

How
I must confess I procrastinated this stuff for quite a while. In some way, it didn't feel totally right to criticize other people's work like I had to do. But after all, by writing this I'll open my work to critics as well. So one evening, at about 10 PM (yeap :-), I printed out the wikipedia page on bowling, fired up a CASE tool (just for drawing), and got busy.

Unfortunately, I can't offer you a realistic transcript of what I thought all along. When you really use visual modeling, you're engaged in a reflective conversation with your material (see my paper, "Listen to your tools and materials"; I should really get a pre-publishing version online). You're in the flow, and some reasoning that will take a while to explain here can take just a few seconds in real time.

Anyway, I can offer a few distinct reflections that took place as I was reading the wikipedia page and (simultaneously) designing my solution. Note that I entirely skipped domain modeling; the domain was quite simple, and the wikipedia page precise enough. Also, note that I felt more comfortable (coming from 0-knowledge of bowling) reading wikipedia, not reverse engineering requirements from the XP episode. That might be my own personal bias.

I immediately saw quite a few candidate classes: the game itself, the player, the pins, the lane, the frame, the ball. Bertrand Meyer said that classes "are here for picking", and in this case he was quite right.
I never considered the throw as a class though. Actually, "throw" can be seen as a noun or as a verb. In this context, it looked more like a verb: you throw the ball and hit pins. Hmmm, guess what, they didn't look at it this way in the XP episode; they sketched a diagram with a Throw class and rushed into coding.

Rejecting classes is not easy without requirements, even informal ones. For instance, if I have to design a real automatic scoring system, there will be a sensor in the lane, to detect the player crossing the foul line. There will also be the concept of foul ball, which was ignored in the XP episode. So (see above, "fair comparison") I decided that my "requirements" were to create a system with the same capabilities as they did. No connection to sensors, no foul ball. But, I wanted my design to be easily extended to include that stuff. So I left out the Lane class (which could also encapsulate any additional sensor, like one in the gutter). But I wanted a Ball class; more on this later.

Game is an obvious class: we need a starting point, and also a sort of container for the Frames (see below) and for Balls. The Game has a need to know when the current frame is completed (all allowed balls thrown, or strike), so it can move to the next frame. But while advancing to the next frame is clearly its own responsibility, knowledge of frame completion is not (see below).

The Frame looked like a very promising abstraction. If you look at the throwing and scoring rules, it's pretty obvious that in 10-pin bowling you have 9 "normal" frames and a 10th "non standard" frame, where up to 3 balls can be thrown, as there is the concept of bonus ball (hence the imprecise domain model in the XP episode, where they just wrote 0..3 throws in a frame). Also in 3-6-9 bowling you have "special" frames, and so on.
Does this distinction between standard and nonstandard frames ring the bell of inheritance? Good. Now if you want the frame to be polymorphic, you gotta put some behaviour in it. Of course you can do that by reasoning upon the diagram: you don't need to write test code to discover that hey, you dunno what method to put in a Frame class.
Anyway, they may not know, but I do :-). The Frame can then tell the Game if it is completed, shielding the Game from the difference between a standard or last frame (or "strike frames" in 3-6-9 bowling, for instance). To do so, the Frame must know about any Ball that has been thrown in that frame, so it may also have a Throw method. The Frame should also calculate its own score. Wait... that's something the Frame can't do on its own.

In fact, the two authors have been smart enough to pick a problem where trivial encapsulation won't do it. The Frame needs to know about balls that might have been thrown in other frames to calculate its score. Now, there are several ways to let it know about them, and I did consider a few, but for brevity, here is what I did (the simplest thing): the Score method in class Frame takes a list of Balls as a parameter; the Game keeps a list of Balls and passes it to Frame. The Frame just keeps track of the first Ball thrown in the frame itself. From that knowledge, it's trivial to calculate the score. The LastFrame class is just slightly different, as the completion criteria changes to account for bonus balls; I also have to redefine the Throw method, again to account for bonus balls.

So what is the real need for a Ball class? Well, right now, it keeps track of how many pins were hit, and its own index in the Balls list. However, it's also a cheap option for growth. More on this next time, where I'll talk about the difference between having even a simple, non-encapsulated class like Ball, and using primitive types like integers.

What
In the end I got something like this (except I had no Status: in the beginning, I put a couple of booleans for Strike and Spare).



At that point, I decided to move to coding. In the XP episode the selected language is Java, but I don't like the letter J much :-) so I used C#. Given the similarity of the languages, the difference is irrelevant anyway. I didn't even want to wait for Visual Studio to come up, so I just started my friendly Notepad and typed the code without much help from the editor :-). Classes and methods were just a few lines long anyway. At that point it was 11 PM, and I called it a day. (So all this stuff took about 1 hour, between BUFD :->> and coding).

Next morning I was traveling, so I started Visual Studio, imported the code, corrected quite a few typos to make it compile, then imported the test cases from the XP episode, converted them to the NUnit syntax, and hit the run button. Quite a few failed. I had a stupid bug in LastFrame.Throw. I fixed the bug. All tests succeeded. While playing with the code, I saw an opportunity to make it shorter by adding a class (an enum actually) that could model the frame state. I did so in the code, as the change was simple and neat. I also brought back the change in the diagram, which took me like 10 seconds (too much for the XP guys, I guess). I run the tests, and they failed, exactly like before :-).
I could blame the editor and the autocompletion, but in LastFrame.Throw I had something like

if( status != Status.Spare && status != Status.Spare )

instead of

if( status != Status.Strike && status != Status.Spare )

Ok, I'm pretty dumb :-), but anyway, I fixed the bug and got a green bar again.

That was it. Well actually it wasn't. In my initial design I had a MaxBalls virtual method in Frame. That allowed for some more polymorphism in Game. In the end I opted for a constant to make the code a few line shorter, since the agile guys seem so concerned about LOC (again, more on this next time). I'm not really so line-conscious in my everyday programming, so I would have usually left the virtual method there (no YAGNI here; or maybe I could restate is You ARE Gonna Need It :-)).

So, here is the code. Guess what, if you remove empty lines, it's a few line shorter than their code. If you remove also brace-only lines, it's a few lines longer. So we could call it even. Except it's just much better :-).

Ok, this is like the longest post ever. Next time I'll talk a little about the readability of the code, give some examples of how this design/code could be easily changed to (e.g.) 3-6-9 bowling, digress on structural Vs. procedural complexity, tinker a little with real options and under/over engineering, and overall draw some conclusions.


internal class Ball
  {
  public Ball( int pins, int index )
    {
    hitPins = pins;
    indexInGame = index;
    }

  public int hitPins;
  public int indexInGame;
  }

internal class Frame
  {
  public const int MaxBalls = 2;

  public virtual bool IsComplete()
    {
    return status != Status.Incomplete ;
    }

  public int Score( Ball[] thrownBalls )
    {
    int score = 0;
    if( IsComplete() )
      {
      score = firstBall.hitPins + thrownBalls[ firstBall.indexInGame + 1 ].hitPins;
      if( status == Status.Strike || status == Status.Spare )
        score += thrownBalls[ firstBall.indexInGame + 2 ].hitPins;
      }
    return score;
    }

  public virtual void Throw( Ball b )
    {
    const int totalPins = 10;
    if( firstBall == null )
      firstBall = b;
    if( b == firstBall && b.hitPins == totalPins )
      status = Status.Strike;
    else if( b != firstBall && firstBall.hitPins + b.hitPins == totalPins )
      status = Status.Spare;
    else if( b != firstBall )
      status = Status.Complete;
    }

  private Ball firstBall;
  protected Status status;
  protected enum Status { Incomplete, Spare, Strike, Complete } ;
  }

internal class LastFrame : Frame
  {
  public new const int MaxBalls = Frame.MaxBalls + 1;

  public override bool IsComplete()
    {
      return ( status == Status.Strike && bonusBalls == 2 ) ||
             ( status == Status.Spare && bonusBalls == 1 ) ||
             ( status == Status.Complete );
    }

  public override void Throw( Ball b )
    {
    if( status != Status.Strike && status != Status.Spare )
      base.Throw( b );
    else
      ++bonusBalls;
    }

  private int bonusBalls;
  }

public class Game
  {
  public Game()
    {
    const int MaxFrames = 10;
    frames = new Frame[ MaxFrames ];
    for( int i = 0; i < MaxFrames - 1; ++i )
      frames[ i ] = new Frame();
    frames[ MaxFrames - 1 ] = new LastFrame();
    int maxBalls = (MaxFrames-1)*Frame.MaxBalls + LastFrame.MaxBalls;
    thrownBalls = new Ball[ maxBalls ];
    }

  public void Throw( int pins )
    {
    if( frames[ currentFrame ].IsComplete() )
      ++currentFrame;
    Ball b = new Ball( pins, currentBall );
    frames[ currentFrame ].Throw( b );
    thrownBalls[ currentBall++ ] = b;
    }

  public int Score()
    {
    int score = 0;
    foreach( Frame f in frames )
      score += f.Score( thrownBalls );
    return score;
    }

  private Ball[] thrownBalls;
  private Frame[] frames;
  private int currentFrame;
  private int currentBall;
  }

Labels: ,

Comments:
Carlo,

would you have a look at
this code
?

It's a simple procedural (should I say functional?) solution, no OO and no diagrams. Just one class and 33 lines of code (excluding empty lines, comments and braces). It's even simpler than the "Agile" implementation. Yet it seems to me perfectly adequate to the task at hand.

I just created a class that stores the input data (a sequence of integers) and the output data and recalculates the latter every time the former changes.

It took me longer to develop and debug it than it took you to do it with yours, but that's to be blamed on the lousy programmer, I suppose :-).

It could of course be improved by adding a "Ball" data type to account for foul balls, but I decided not to do it to keep the same interface of the original (and also because I wasn't 100% sure that such a foul ball could actually exist, I just had a quick glance at Low Ball Bowling). In any case, adding that at some later time would be pretty easy, and it wouldn't even require breaking compatibility with the old interface.

Compared to yours, this approach, apart from being shorter and simpler, is (IMHO) easier to read and understand exactly because of its procedural nature.

It also quite easy to change it if the rules do. You can adapt it to, for example, the made-up rule you mentioned (7th frame is allowed only one ball) by only changing (slightly) five lines of code and adding another five (
here
).

I've nothing to criticize about your solution (though if I had had to build something more "sophisticated", I would have done something different (
here
), it's slightly longer than yours, but some of the classes could be reused in completely different contexts), I'm just saying that a simple solution doesn't necessarily have to be a mess, and in fact it's at times preferable.

What do you think?

P.S.
You made similar comments in an old post, saying that a procedural approach to a Sudoku solver would lead to a "mess of procedural complexity". Needless to say, I disagree. Sooner or later I'll implement it, and then I'll come back to you. I'd like to discuss it.
 
Anche se non in un approccio TDD, hai comunque usato i test che hanno scritto loro. Avresti scritto qualche tipo di test (in un contesto reale) ?
Cosa puoi dire della produttività nello scrivere i test (== bachi trovati / tempo impegato nello scrivere i test) legata oltre che al dominio (che normalmente è dato), a quanto li consideriamo importanti (previsioni autoavveranti) e a quanto il design può rendere meno bacato il codice (ex ante) ?
 
Ciao Andrea,
scusa se ti chiedo delucidazioni... ma il tuo intervento si riferiva a Carlo o a Zibibbo.
Penso che ti stia rivolgendo a Carlo (ma questo perché hai scritto in italiano :p), e proprio per questo intervengo.
Dal mio punto di vista, anche se si fa una buon design del problema, non si può evitare di scrivere del codice di test... quello che cambia nell'approccio XP (se non erro) è che i test guidano lo sviluppo... e quindi "solo se si trova un test interessante per un determinato sotto problema, si implementa la soluzione" (estremizzando).

Ciao,
Eros
 
Hi Zibibbo,
the main difference I see between Carlo's code and yours is related with code maintenance. In fact in order to have two different kinds of game you have two separated codes; if a bug will find in a common piece of code, you will modify the same code two times.
Sure, in this simple application, the problem is not big... but in real applications could be.

Cheers,
Eros
 
Zibibbo, great comment. By the way, you're the good guy I mentioned in the first post on bowling. I'm glad you're the first to answer, and that you do that with code, not with metaphysical theories :-). Hope you set the tone.

Now, it seems like there is a tiny bug in your code :-), as it does not pass the testSimpleFrameAfterSpare and testSimpleSpare. By looking at it, it seems to me that you're missing the concept of incomplete frame (only one ball thrown), which does not contribute to the score.
I'm sure the "right" code is just a few lines away, so we can leave that aside and get down to business. You raise a number of interesting observations.

- Your solution (even after introducing the concept of incomplete frame) is shorter than mine, and shorter than the one in the XP episode. It's not full of literals like their code, and it seems to me to be written at a good abstraction level, using domain concepts and not fighting an implementation. I still see some feature envy, but anyway :-).
In some sense, while I aimed at fixing issues by adding structure, you aimed at fixing them by simplifying structure. We both succeeded, and I guess you didn't use TDD/XP either: looking at your code, I would say you used mostly top-down decomposition (plus test&fix), but that's a wild guess. Anyway, you reinforce my point that the process described in the XP episode did not, on this specific case, produce a staggering result.

- You also proposed a more sophisticated solution based on a state machine concept. This is closer to the idea of the “bowling framework” I was talking about, so I’ll wait till my next post.

- The central issue of your post (and the previous one on Sudoku) seems to be Procedural vs. OO, at least for a category of problems, which we could classify as “inherently algorithmic”. Now, your sentiment is widely shared in some communities, like scientific/engineering computing (indeed, I’ve been wondering if you come from that community).
Broadly speaking, there are many people who, for one reason or another, believe that procedural code is intrinsically simpler than OO code. This is part of a Function vs. Form issue that I’ll partially cover in my next post, but it’s a wide subject which I should discuss more deeply in the future. Right now, I’d like to take the incredible opportunity :-) you gave me by posting code to show a few issues with the procedural approach.

Don’t get me wrong, structured design and structured programming are both extremely valuable, and OOD/OOP is still solidly resting on the techniques and concepts born back then. However, procedural code is a “rigid” material, which breaks easily. Take your example: to introduce the concept of “7h frame is only allowed one ball”, you slightly change 5 lines and add 5 lines. This is not bad in itself, but if you look at the code, you’ll see that those changes are spread basically everywhere. Any procedure that was doing something useful had to be touched. If you accounted for incomplete frames, you may have had to touch that code too, depending on how you wrote it (1 ball is incomplete for other frames, but not for the 7th). This is because, by organizing your code around the principal decomposition of functions, you get fragility along other axis (changes in data, changes in functions which are not part of the principal decomposition, and so on). Indeed, you had to pass an additional parameter too (fragility on data).
You also lose the previous version of your code: you now have to maintain two versions, or you might add flags to "enable" the special 7th frame (another sign of procedural mess).

Contrast this with what you have to do with a more plastic material, where data and functions are grouped together and you can exploit polymorphism. With my design, you should just create a SingleBallFrame class, derived from Frame. Add very little logic inside (all new code, you don’t break existing stuff). Then, because I didn’t create a real framework, you have to take care of the creational dependency inside Game (couple of well isolated lines anyway). That’s it.

It’s also a matter of understanding and unit testing. You said that the procedural nature makes it easier to understand. I’ll get back to this in my next post, but you should also consider that (like in your case) many procedural programs have to be understood (and tested) in their entirety (because procedures get coupled in many subtle ways), while OO programs can be first understood at the architectural level, then at the class level, then back again at the [detailed] integration level. This is to say, if I get my SingleBallFrame class working in isolation, there is quite a good chance it will work inside the big picture as well. I don’t have to mess around with working code.

This brings me back to the “procedural mess”. Procedural code can easily get messy, because any small change must be accounted for by flags, additional parameters, additional if/then/else spread around (possibly the same conditional in different functions, because the flag is orthogonal to the principal decomposition of functions), and so on.
Procedural code is also commonly [but not always] created upon a layer of primitive types (in Sudoku, chances are you’ll just use a matrix, without any concept of row, column, 3x3 square). That, again, removes us from the problem domain, and makes the code messy. How do you apply the same function to a row, a column, a 3x3 square if you don’t have those concepts? Usually by flags telling the function how to increase the (i,j) indexes. This might look fine in scientific programming, but it’s not really nice code.

Back to the design board: I’ve often said here that good design is a matter of balance. I’ll take your code as a successful demonstration that by using a procedural approach, you can optimize the LOC count quite bit.
We can also take the opposite approach, forget about LOC, and design a very flexible framework where everything is isolated, and implementing (e.g.) 3-6-9 bowling is just a snap.
We can also try to get a decent structure without exceeding with LOCs. This is basically what I wanted to do, to show that balance can be easily achieved by not rushing into coding, and that we can take several decisions on a diagram, if we learn how to use it properly.

Thanks again for your hands-on approach, I really appreciate!
 
Andrea, anche tu poni eccellenti questioni!

Sicuramente avrei pianificato dei test. Dopodiche', in un caso cosi' elementare, e' talmente semplice scriverli in modo automatico che e' un delitto non farlo.
Forse li avrei scritti diversamente, ovvero organizzati per testare in isolamento Frame e LastFrame, e poi insieme. Magari lasciando, in un caso reale, qualche test di integrazione ai tester.
La mia filosofia di fondo, in molti casi concreti, e' che io cerco di avere dei tester nel gruppo, ma cerco anche di dare loro del codice ben testato.
Sempre rimanendo sul concreto, salvo casi davvero elementari come questo, trovo ingenua l'idea che uno si scriva i test mentre programma, sperando di beccare tutti i casi significativi.
Un esempio concreto: in un sistema a cui sto lavorando, ho un componente che deve gestire un certo numero di situazioni, per lo piu' anomale ma recuperabili, derivanti da un piccolo numero di variabili, ognuna delle quali riconducibile ad un piccolo numero di valori.
Prima di scrivere codice o progettare alcunche' (e quindi questa e' una forma di TDD, per quanto molto diversa da quella normalmente predicata in XP) io mi sono calcolato quante situazioni differenti dovevo gestire. Erano 108. A quel punto ho cercato di trovare un albero di decisione "ottimale" a manina, che mi evitasse 108 if nel codice (o una tabella con 108 entry). Dopo un po' ho deciso che mi serviva un programmino che mi aiutasse a non sbagliare, quindi ho scritto (sono poche righe, l'efficienza non e' importante) il programmino.
Mi e' uscito fuori un albero decisionale con una ventina di decisioni. Ora si tratta di implementarlo. I test case sono progettati, il codice progettato, non mi aspetto deviazioni significative ma i test li faremo lo stesso. Se noti e' una specie di TDD ma ugualmente upfront. In generale, io trovo molto ingenua l'idea che ci si poteva sedere davanti al PC, con lo stesso problema, iniziare a scrivere codice e test, ed arrivare ad un risultato decente. Funziona sui tipici programmini che si vedono negli esempi, funziona gia' maluccio in semplici programmi corporate che tirano fuori un report e che butti via il giorno dopo, funziona male quando il dominio e' intrinsecamente complesso.

Due parole (sarebbe un discorso molto bello e molto lungo) anche sulle self-fulfilling expectations. Io progettando quel diagrammino del bowling non mi aspettavo bug, ogni funzione aveva poche righe. Era, in un certo senso, progettato per essere bug-free :-), e ci e' andato vicino.
Io tendo a progettare in questo modo, e mediamente le cose vanno come mi aspetto. Tendo anche a progettare in modo da non dover continuamente modificare il codice che ho scritto, e mediamente anche questo si avvera.
C'e' chi scrive codice pensando che mettera' parecchi bug e che dovra' modificarlo di continuo sino a farlo funzionare, e mediamente anche questo si avvera :-).
 
Eros, quello che non mi piace dell'approccio XP, in fondo, è che credo che si confondano obbiettivi e mezzi: è assurdo "lavorare per il verde"! Cosa vuol dire ? Quello che devo fare è al limite "lavorare per avere pochi bachi" ( e anche questo aspetto è dipendente dal dominio: bisognerebbe parlare in generale di valore e il discorso si complica alquanto). Comuqnue sia il testing (in particolare il testing di unità) è uno strumento che in alcuni casi e in alcuni contesti può essere utile o addirittura quasi scontato (come dice Carlo "un crimine non usarlo"), ma in taluni è fuori luogo o non è la cosa ottima o va affiancata ad altri aspetti/accorgimenti/tecniche. Incidentalmente la cosa più sbagliata che mi è capitato di sentire è l'uso come "scusa" per giustificare tempi più lunghi o l'assenza di features implicite. In modo coerente in fondo con la considerazione che il test di unità abbia un valore in se stesso e non sia uno strumento per aumentare il valore del prodotto (per il quale spesso il tempo è il fattore principale).
 
Carlo, condivido davvero completamente quello che dici!
L'unico dobbio che mi rimane riguarda qualche considerazione su quanto sia facile/diffcile ottenere certi risultati facendo applicare in modo relativamente "rigido" e molto guidato una certa metodologia a programmatori diciamo non troppo qualificati e/o esperti... per capirci che tu ottenga certi risultati non è probabilmente completamente significativo rispetto alla reale applicabilità a certi contesti produttivi ;)
 
1) Visto che ci fai continuamente riferimento credo che sia arrivata l'ora di mettere online un qualche draft di "Listen to your tools and materials".

2) Su self-fulfilling expectations: daccordo che pensare positivo aiuta, ma la differenza è che se lo fai tu che c'hai non so quanti anni di esperienza ci credo che ci vai vicino al bug-free o che so io, se lo faccio io (e ti giuro che parto con tutte le buone aspettative e il pensiero positivo possibili :) il risultato è cmq diverso. Magari ci provo, ma a questo punto non vale più. Peccato non aver avuto tempo prima.

3) sulla classe ball. Io ad esempio l'avrei marcata (almeno per il momento, anche se hai anticipato sviluppi) come association class, perchè il suo unico scopo per il momento è tenere l'assocazione n° del tiro-birilli buttati a terra. Ne avrebbe risentito meno il mio occhio a vedere una classe senza comportamento :)))!

4) Mi fai una sorta di pubblicità a qualche strumento CASE recente e decente? o tentato di utilizzare visual paradigm ma per quanto abbia qualche spunto interessante non mi ci trovo benissimo con la loro UI!
 
Andrea, non so bene dove ho dato l'impressione di proporre un metodo rigido (magari non in questo post?). In realta' e' proprio il contrario, da cui il mantra "design is highly contextual", ma piu' in generale, la mia idea che proprio XP e compagni siano troppo rigidi, perche' mentre da un lato vedo predicare "Individuals and interactions over processes and tools" nella pratica non si vede dove stia l'adattamento agli individui ed alle situazioni, visto che alla fine gira tutto intorno a quelle quattro idee code-centric, e chi non la vede cosi', va rieducato :-).
Quindi, prima ancora di tutte le considerazioni fatte qui, se ne devono fare altre: chi sono le persone che dovranno lavorarci su, che risultato vogliamo avere, cosa e' realmente il successo per quello specifico progetto, quali sono le condizioni al contorno. Una demo per una fiera importante puo' valere piu' di tanto codice scritto con cura che nessuno vede e nessuno compra. Credo di averne parlato in altre occasioni.
In effetti anche in questo caso specifico dicevo: leggendo l'XP episode, ho intuito la possibilita' di ottenere un risultato migliore partendo da un modello. In due progetti che sto seguendo, stiamo invece usando pratiche molto code-centric, per ragioni diverse nei due casi e molto dipendenti da una serie di fattori al contorno (non tecnologici).
Dopodiche' (e poi la smetto :-) e' ovvio che non possiamo prendere un bravo ragazzo appena uscito dall'universita' e sperare che ci tiri fuori al volo delle architetture mirabolanti. Le persone vanno seguite, formate con cura nella teoria ma soprattutto nella pratica, con un confronto costante e non del tutto occasionale, sperando che diventino non solo autonome ma possano poi seguire gli altri. Ovviamente e' un investimento anche significativo, che va fatto a ragion veduta, scegliendo le persone giuste...
 
No, Carlo, non mi hai dato mai la sensazione di proporre un modello rigido, né qui né altrove! Anche perchè uno degli aspetti centrali che metti in evidenza è che anche la scelta delle tecniche e/o metodologie migliori da applicare è fortemente contestuale.
Più ingenuamente io intendenvo invece che comunque alcune tecniche potrebbero "in generale" essere più facilmente applicabili o almeno garantire più facilmente una sorta di livello minimo anche in ambienti relativamente poco "qualificati" e che quindi in questi contesti abbia senso l'ipotesi di forzare l'applicazione di modologie con degli aspetti di rigidità od anche eventualmente con dei limiti nella qualità degli obbiettivi raggiungibili.
 
Fulvio:
1) abbiate fede :-)
2) non e' esattamente una questione di pensiero positivo, ma di azione positiva.
Se di solito metto 1 bug ogni 10 righe, poi un giorno in modo molto convinto penso "ora non avro' nemmeno un bug" e scrivo il mio codice come sempre, magari mi si manifesta lo spirito del pensiero positivo e invece di 100 bug in 1000 righe ne metto 99 :-) ma tutto finisce li'.
Se invece agisco concretamente per organizzare il mio codice in modo tale che ogni metodo sia corto, semplice, che ogni classe sia ben coesiva, ecc ecc, la probabilita' di diminuire la densita' dei bug aumenta. Non serve che ti ripeta la massima di Dijkstra, so che la conosci bene :-).
3) Non e' affatto male la tua idea! Alla fine, se ci ripensi dopo la prossima puntata, vedrai che comunque anche seguendo la strada che suggerisci si potevano avere buoni risultati.
4) Uff... :-) non ne conosco. Alla fine mi trovo sempre con queste due scelte:
- da un lato il vecchio Rose, che nemmeno capisce bene l'UML 1.4, figuriamoci il 2, aveva almeno 2 zeri di troppo nel prezzo, ma che e' happy-snappy quando disegni e non si mette nel mezzo.
- dall'altro dei prodotti piuttosto completi nel supporto di UML 2, spesso abbastanza economici, in genere con una GUI un po' fracassona che si scopiazzano a vicenda, talvolta aggiungendo ingredienti vergognosamente invasivi, tipicamente scritti con la J, per cui quando tiri una riga ci sono tutti gli elettroni impazziti che corrono nel processore gridando "tira una riga! tira una riga! aiuto!" e se guardi bene vedi i singoli pixel che faticano ad apparire :-).
Ergo, attualmente il mercato dei CASE e' deprimente e farei fatica a consigliarne uno senza 10 pagine di disclaimer...
 
Carlo,

just a quick update.

When I posted the version of the code that handled your made-up rule, my only aim was to show that figuring out what changes to make to the code was very straightforward, something that was not true for the solution described in the XP episode. I didn't mean to show how I would have structured the code if I had had to support different varieties of bowling within the same application or library. But the thing backfired on me, so now I've to fix that.

This
is what I would have done if I had foreseen the need to support multiple versions of the game. I also tried to fix the bug you mentioned, but I'm not sure I've got it right, I just copied your code because the tests you mentioned did work for me (did you add new assertions to them?).

The LOC has gone up a bit (37 lines), but you get the same flexibility as in your solution, or so I believe. Trying to achieve simplicity doesn't mean that you cannot use OOP techniques, if you see fit.

I'd also like to add that when I say that procedural code can be more intuitive and easier to understand, I'm mainly thinking of the "programming in small" case. This was the typical example, a small subsystem that is isolated from the rest of the code by a simple and well defined interface.

As for the Sudoku issue, please defer judgment until I have some code to show. It won't be to soon, though...
 
Zibibbo,
Thank you again for replying by writing code. I really like your style :-). Once again, you give me an opportunity to show, in practice, the meaning of abstract concepts like "design as balance" and the importance of structure.

Just a small detail about the failed test cases: I didn't publish the test code, but it was just the XP episode code translated into NUnit syntax. Is it possible that you missed some [Test] attribute? If you look at your former code, it's quite easy to see that it will fail (e.g.) the testSimpleSpare case, calculating 16 instead of 13 because of the incomplete frame. Anyway, you new code fixes the problem, although it seems to me that you left a dangling ": Game_Bowling" in the base class (as it is, it won't compile).

That said, we should now admit that your code is no longer procedural. It is mostly procedural in the base class, but it's now full-fledged OOP. In fact, speaking in term of patterns, you have done quite the sensible thing, and adopted the template method for almost every member function. In other words, you have implemented a "complex" algorithm in terms of steps, then made each step virtual, so that it can be redefined in a derived class. That gives you more flexibility (as you said) while keeping the class count (and the overall structural complexity) low.

Unfortunately, flexibility always comes to a price.

In your case, for a starter, you decided to change the implementation of the list of throws, from an array to a linked list. I can only presume why you did it: because the number of throws was no longer "fixed" in the base class (accidentally, you left an unused MAX_NUM_OF_THROWS constant in your code; you guys should really compile with warnings turned on :-)).
Of course, you could have done this differently, e.g. by having a virtual method in the base class to calculate the maximum number of throws, and by redefining the method in the derived class. More lines, more OOP. Note, however, how your decision to use a list (something I did consider too when I designed my version) brought you into a little fight with the implementation, manifested by the need to use safe_get_throw to access the list. This bears little resemblance with the problem domain and obscures the code a little.
You also had to compromise on efficiency (not really an issue in this case), because using the subscript [] operator on a linked list has linear cost, whereas on an array the cost is constant.
Overall, an array would have been a better solution, although one more virtual function would be needed. Interestingly enough, there would be some redundancy between the base class implementation of MaxNumberOfThrows() and the implementation in the derived class: by removing the Frame abstraction, you cannot have any separation of concerns between the (possibly specialized) frames (which knows their own rules on allowed throws) and the Game (which knows the type and number of frames). More on this in a short while.

So far, you may see this as nitpicking (I don't, but that's me :-)). There is, however, another flaw coming from the procedural nature of the solution. In your base class, you had to pass the frame index to number_of_non_bonus_throws_for_frame and frame_score. This required you to pass it as a parameter to other functions too, like is_complete. Now, the base class' implementation has no use for that parameter. You passed it only because it is needed by the derived class. Sometimes, this is fine. Quite often, this is wrong. You can easily tell, because when you tweak the base class only because you discovered a derived class, and the tweak does not really make any sense in the base class, you're on the verge of the abyss :-)). Another derived class may require you to tweak your base class again, just in a different way, and so on. Base classes cannot be designed (or coded) upon the needs of a single derived class. Otherwise, what you get is an undesired form of coupling. By the way, coupling was a nasty problem in structured design / programming; this is part of the procedural mess.

Now, since I spoke of patterns, we can easily look at the main difference between your OO design and my OO design in terms of patterns: you used template method, I used strategy. My frames are nothing but strategies. Now, strategy comes with a cost in structural complexity (one more class) but allows for higher separation of concerns and composability of behavior. A simple example will clarify the issue.
Assume I ask you to create a third variant of the game: the 4th frame is allowed 3 throws. It would be a piece of cake for you to clone your Game_10PinBowling_WithPescioRule_2 class into a Game_10PinBowling_WithPescioRule_3 class and specialize the behavior there. So far, so good. What if I ask you to create a game with both rules: the 4th frame is allowed 3 throws, the 7th just one. You have basically no choice but to clone that class again, merge code, and accept redundancy. That's a known shortcoming of the template method pattern: it does not allow you to mix different implementations, as there is not enough structure in it.

So that's why I'm talking about design as balance, and why I do not blindly follow principles like YAGNI or just optimize for simplicity. I designed my bowling program that way because it was a good balance between flexibility and simplicity. It allows me to easily compose the behavior of different frames, mixing them together, has no undesired coupling between base and derived classes, does not fight the implementation, and yet is not a super-duper bowling framework (with the additional complexity that will necessarily come with that).

To conclude, I fully appreciate the idea that at some point ("programming in the small") you can just rely on procedural code. However, I can't support the idea that "procedural is simpler", but I'll leave this for another time (I really have to talk more broadly about Form and Function).

About Sudoku, I'll suspend judgment :-) but I'll add a few details on requirements: I don't really want a backtracking solver (which is really trivial). I want a deterministic solver where any number of heuristics can be tried out. If, at some point, no heuristic can be used, the program should stop and present how far it could go. This is much more funny as it allows you to apply human intelligence only when/if simple rules fail. From that, you can also devise smarter (deterministic) rules and try them out. So I guess I'd like to add new rules easily. But that's not strictly a requirement. Hope that's not too hard for a procedural solution :-)))).
 
Carlo,

here's my procedural/functional sudoku semi-solver. Made to your specifications. And I must say, I find it very beautiful.

A brief description of the approach I used: first of all, as you rightly guessed, I used a matrix to store the sudoku grid. To allow strategies to work uniformly on rows, column and subcells (which are all matrices, after all), I just created a submatrix type whose purpose is to selected a subset of the whole matrix and morph it in the form that is best suited to the functions in question. The mapping is of course bidirectional, so updates can be done directly on the original data. In a way, I like to think of this technique as something similar to the concept of views in the relational model.

Also, I got extensibility for free. A procedural/function sudoku semi-solver is nothing but a function that takes a grid and returns another grid (not sure about an OO one, would it still have this property?). And composing such functions is pretty easy, as you can see.

Now it's your turn. I'd like to see what improvements diagrams and a sophisticated OO architecture can bring about... :-))))
 
And I must say, I find it very beautiful.
Zibibbo,
if giving design criticism wasn't already hard enough, saying you find it very beautiful just made it harder :-). I'm not really in a Project Mayhem mood today, so I should just postpone the issue for another day (those unfamiliar with Fight Club may want to look up a quote, look for something beautiful :-).

I'd like to see what improvements diagrams and a sophisticated OO architecture can bring about... :-))))
More seriously, I shall probably delay most comments till I get around my Form Vs. Function post, because your code would make such a perfect example. Because really, it's gonna be fun to show you how diagrams and classes can bring in quite a few significant improvements :-).

A comment can't really wait though: your code is not really procedural (or functional), just like the alternative you give is not really OO. Indeed, your code looks exactly like I said in part 4:
You get your matrix code right :-), and above that, it's naked algorithms all around.
Now, I don't want to sound harsh, but let's be honest: your code is not a total mess only because you used classes in the infrastructure. If you didn't have a Grid class and a SubGrid class providing some information hiding, your code would have been a total procedural mess, with a dozen parameter or so passed to every function (or alternatively, global variables) just to tell them where to work. Skeptics are kindly invited to rewrite Zibibbo's code without using any OOP or pseudo-OOP construct.

Along the same lines, you know that OOP is not about using inheritance to specialize a single large/fat class. OOP is about finding the right fine-grained abstractions in the problem domain, giving them proper names so that you can program against the problem and not against a meaningless abstraction, encapsulating behavior so that you can easily add (for instance) a persistence layer or a GUI without making a mess.
Your "OO" alternative is definitely not built this way, but I promise, after the Form Vs. Function thing has fallen into place, I'll get back to this and show you how I did it. Some portions are similar, in that I have "subgrids" too, except they are named after the problem domain. Funny thing is, when I mentioned an unbalanced structural complexity in the original Sudoku post, I was thinking of those classes, so your solution didn't really take that part of structural complexity away :-).

That said, I want to thank you once again for throwing in some real code. Far too often in this business, we use well-concocted examples where things inevitably play out nicely. It's always good to face a challenge :-). See you soon here!
 
if giving design criticism wasn't already hard enough, saying you find it very beautiful just made it harder :-).

Please, don't fell bad about giving criticism, or be afraid to sound harsh :-). Because another thing that I must say is, having you pick holes in my code is really interesting and instructive. It's part of the fun of posting code here. I just wish I could do it with a real project, one of those where I'm really at a loss about the design, not just with toy examples, but unfortunately I cannot afford to hire a highly paid consultant... :-)
 
No doubt Sudoku is a toy problem :-)).
I've long entertained the idea of organizing a full-day Design Fest once every 3 months or so, where people with an interest in software design could meet and try out different approaches and techniques on challenging real problems (well, possibly cut down to a manageable size). For free of course :-).
Maybe one day I will... :-).
 
Post a Comment

<< Home