mascip has asked for the wisdom of the Perl Monks concerning the following question:
Edit : forget about this thread, i mostly just got very confused and lost in vocabulary, just answering to myself as i was reading and discovering new concepts.
Hello all,
i'm simultaneously reading this book about TDD : "Growing Object-Oriented Software, Guided by Test", and making a small program. And i have a question about design and TDD methodology.
My aim here is mainly to learn TDD, i'm not in a rush, so i can try different designs and learn.
I am trying to respect the "single responsibility principle". As stated in the book, it might sometimes lead to more lines of code and more complicated structures, but it is more maintainable, as you easily find what you are looking for (separation of concerns), and you never need to cut through unrelated functionalities. Which makes for more modulable, adaptable programs.
You end up with more classes, that you can combine easily to create more powerful lower-level objects. The power is not purely in the objects anymore, but in their relationships: how they are assembled together.
~ ~ ~
My program must process hundreds of spreadsheets, each containing measurements, from several experiments. And do complex manipulations and calculations on these data, experiment by experiment.
I started this project by writing an end-to end test for a simply feature : reading 3 lines of two of the spreadsheets, and doing a very simple calculation on it. with the calc_simple_stuff_for_experiment($experiment_directory) subroutine.
After a few minutes the program compiles, and my next task is to implement calc_simple_stuff_for_experiment(). There are three tasks in this subroutine :
1. read the two spreadsheets (into two Data::Measurement objects for example). I will create a Reader::OfMy::MeasurementSheets object for this.
2. gather their data in a common structure : as the measurements are not done exactly at the same time, i need to "merge" them on a common time axis (into a Data::Experiment object for example).
3. do my calculations, for each time period (this is the easy bit).
~ ~ ~
I imagined two ways to implement this simple feature, with strong difference in the designs. They almost seem "opposite", or "symmetrical". I would like your opinions on these two designs.
* Idea 1 (my first intuitive idea) :
I create a Data::Experiment object. This object will have two Data::Measurement attributes. In the constructor (or in a subroutine, just to keep a simple constructor), it "time-merges" information from the two Data::Measurement, into a common structure.
Then i can just do my calculations from this Data::Experiment object.
Or even create a Calculator::ForMyExperiment object, which will take the Data::Experiment object as an attribute, and make calculations on it.
* Idea 2 ("From the book") :
If i try to follow what they do in the book (and which i might have misunderstood), then i start from the input, by creating the Data::Measurement class, that will contain the data that was read from the spreadsheets. (and even the Reader class, before that)
Then i need to delegate the responsibility of merging the data on the time-axis, and to do this i follow the method used all along this book (have i misunderstood? It was not clearly stated, but it seemed to me that they are always using this same methodology, every time the words "delegating" and "responsibility" appeared) :
- i create a Role to represent the responsibility : Merges::Measurement::Data::On::TimeAxis
- then i create a class to consume this Role : Data::Experiment.
- and i "plug" this new class to Data::Measurement, by giving a Data::Experiment attribute to the Data::Measurement objects, and passing it all the necessary information to fulfill its task : a reference to the data hashes.
Which would mean that i would have to create a Data::Experiment object in my script, and give its reference as an attribute to the two Data::Measurement objects.
Then, same thing for the Calculate::Simple::Stuff role: it would have to be consumed by the Calculator::ForMyExperiment class, which would be "plugged" as an attribute to the Data::Experiment class.
So in this second design, the relationships are reversed : Data::Measurement has a Data::Experiment attribute, which has a Calculator::ForMyExperiment attribute. Exactly the opposite/symmetrical of the first proposed design.
Right now, to me, the first design still feels more "healthy" than the second one. Simply because for me, intuitively, an Experiment "has" Measurements, rather than the opposite. And a Calculator "has" Data rather than the opposite. This is how i was taught attributes.
~ ~ ~
Writing these lines, I imagined a very naive explanation : maybe that
in their case (for the example of this book), they delegate the responsibility to an attribute, because the information that is transferred consists only in orders : "do this, do that".
In my case, i need to do the opposite (delegate the responsibility to the "parent") because what i am transferring is data. The parent already knows what it must do, i am not giving it any order.
So...maybe i am seeing this problem from the wrong angle. Maybe that what they call "input" in this book (when they say "Develop from the Inputs to the Output") is an input of orders, not an input of data.
But this explanation doesn't feel right either. In the book, (p113 for example), they start "from the data", and start by creating a "auction message translator", which will then "delegate" the responsibility of responding the message, by putting the next class as an attribute to the "auction message translator". So, they end up with an "auction message translator", which "has" an "auction sniper". It seems to be the exact opposite of what i was taught at school.
I am confused, confusled, discombobulated. What do you think ? Is this second design good too? Better than the first one ? Are they both valid, simply deriving from different ways of viewing responsibility delegation ?
~ ~ ~ * EDIT : *
PS : for those who haven't read the book, i found this example (in Java, but it's fairly simple) here (http://www.methodsandtools.com/archive/archive.php?id=90) to illustrate the method from this book, for delegating a responsibility :
" The concept of SingularTask represents a singular focused activity that is delegated to a Worker. If a SingularTask is started, it will ask someone or something to work on the WorkItem. Do we need a Person for this? Possibly, but let's focus on the role instead and call it Worker. The corresponding test is:
We specified that the worker should receive the message workOn(workItem) when the task is started. The corresponding implementation is:@Test public void startShouldMakeWorkerWorkOnWorkItem(){ Worker worker = mock(Worker.class); WorkItem workItem = mock(WorkItem.class); SingularTask task = new SingularTask(worker); task.start(workItem); verify(worker).workOn(workItem); }
"public class SingularTask implements Task { private Worker worker; ... public void start(WorkItem workItem) { worker.workOn(workItem); } ... }
So, like i said before, with this methodology, for the Task to delegate its work, i create a Worker role (i would have called it Process::Work, or Do::Work), which will do the work (on the item). And put it as an attribute to the Task.
I will read the whole article tomorrow, when it's not the middle of the night.
~ ~ ~ * EDIT bis (PPS) : *
Finally i read a bit more. If i understood well, both designs would be "valid".
The first one correspond to a "bottom-up" build, where you first build the core (low level components) of your application. The second design correspond to a "top-down" approach, where we first consider "high level" classes, which will deal with "the real thing", and later build the core of the application.
There seem to be lots of discussions out there, about which method would be better than the other. Maybe that different methodologies suit different people. I'll try a bit of "top-down" for a while, to get the feeling of it. I think that when i code "bottom-up", with my low experience, i tend to code a lot too many things, features that i don't need later. "top-down" might help me become more "realistic".
Am i getting this right ?
And sorry for a very long message. Writing me helped me understand my questions, and hopefully there will be some interesting feedbacks and-or discussions.
|
|---|