views:

136

answers:

3

I've set myself the task of implementing a real time MIDI application. Like all the other software I've written to date, I began by coding. I implemented a tiny GUI (GTK2) application that can control the transport state of the Jack Audio Connection Kit and its clients.

I have never written a real-time application before, and have only ever written one multi-threaded program. Both of these details combine to make this a substantial challenge for me as all the software I have written to date, has not required me to design it first. I've only occasionally needed a pen and paper to work things out.

This project however, won't let me proceed by coding. But I know next to nothing about software design, I am self taught (discounting a 2 yr computer studies course in the mid 1990s). I've always worked stepwise, getting something working and then building upon it.

During my research I've come across the Model View Controller pattern but I'm finding it really difficult to not think about details, and can't find any foundation to build upon without finding problems which bring it all tumbling down.

I need advice to get past this block. I need to stop finding distractions which loose my train of thought. This is one of the distractions. How do I get past this block?

A: 

One way would be to discuss your requirements and very ruffly scetch out a design with someone who is familiar with designpattern and designing software. During this process you could discuss the concepts of the applied designpattern.

bitbonk
+3  A: 

In very broad terms, "software design" is the process in which you decompose the problem at hand in a series of module, and specify what each module responsibility are, and how each module should communicate with the other modules (if at all).

There are various ways to proceed with this activity. Considering this would be your first attempt at it, keep things as simple as possible: grab some paper sheets and a pen.

First step: write down a list of tasks that your new application must be able to execute. After this try to group the tasks in different sets that you think belong logically together.

For each subset, find a name, write it on blank sheet (one for subset) and describe what the module does in a little more detail, including which type of data it should work on and/or exchange with others. Each of these is a "module design paper"

On another blank sheet draw a box for each subset, label it with the proper name, and try drawing arrows from one box to the other, each arrow should have a name and represents one of your module calling another. Let's call this your module "interface design paper".

Double check your modules description with the interfaces they should offer to other modules, and see if this requires change to the original task list and how this affects the data they manage.

Modules can be subdivided iteratively if they look too complex/big to you. Just draw another interface design paper if you split a module in submodules, and remember that the sum of the submodules should be able to carry on all the tasks you had originally envisioned for your module, and be able to answer the requests of the rest of the system.

See also CRC cards for more detail.

p.marino
+1 Great explanation. Perhaps you could add TDD and User Stories to the mix.
Lieven
The list of tasks exemplifies the type of problem I'm having. On the one hand, there are the tasks the user will be undertaking and experiencing, and on the other, there are the tasks that I know as a programmer will need to happen to achieve this user experience. I'm having difficulty separating the two, or put another way, difficulty maintaining the level of abstraction to describe these tasks (if that makes sense?).
James Morris
Of course some are user performed... but the user will have to perform these by selecting from a menu, or depressing a button... and the button will have to invoke the interface to one of your modules. When I say "Interface" I am not focusing on GUI, but what the various modules can do for each other. The "services" they offer to the whole application.
p.marino
@Lieven: I'd prefer not to complicate things too much. Also, this is so broadly defined that I could replace the CRC cards with Structured Analysis links and it would still be pertinent. In other words, basic design principles were in use long before TDD and User Stories, and in the case of the OP, I think that this should already be an improvement.
p.marino
I took your advice - following a process loosely based upon the CRC cards you mention. It did help, and also brought up some considerations I had not thought of. To begin with I was still trying to think in terms of the MVC design. It turned out I misunderstood the pattern - the model is the data structure (not the jack_process callback), the jack_process callback could also be considered a view. See: http://lalists.stanford.edu/lad/2010/02/0293.html
James Morris
I am glad it helped: a bit of "up front design", even if it's not very formal, helps *a lot* at least in my experience. It helps both in terms of exploring alternatives and identifying problem areas, and in having a sort of bill-of-materials of the things you have to develop.
p.marino
+2  A: 

The answers already written for this question are great but none of them mention the most unique part of real-time software development: the real-time technical requirements.

You need to figure out exactly how responsive your software needs to be, how large of a memory footprint it may hold, how fast boot-up may be, and how large the executable may be. The if you're on a normal PC, the memory requirements may not be as important to you, but the run-time speed requirements will be important whatever your target platform is.

If your high-level technical requirements are "good enough that the user is happy", then you'll only have to worry about the low-level technical requirements -- basically, what your target computer, your target OS, and your third-party libraries can handle.

It sounds like you've written some low-level code already. I would recommend writing the rest of your hardware/OS/library interface code and time each piece of it, both for error conditions and the happy path. This will give you a much better idea how your code should treat each of its interfaces. (For example: The timeout on this utility call is long enough to make my application back up! I'd better see if I can shorten the timeout or have some better error-checking before I call it!)

Finally, most real-time software is written as a loop something like so:

while( program_running )
{
  // This period needs to be long enough for you to do your work
  //  but short enough that your user doesn't think your program
  //  is choppy. Anything better than 50 Hz is usually good enough
  //  for an application with a human interface.
  wait_for_short_period() 
  check_interfaces_for_new_data()
  update_model() // or state machine
  update_outbound_interface() // the speaker, monitor, whatever
}

There are variations on this (periodic callbacks instead of waits), but that's the general idea.

thebretness
Interesting answer. I've found more information since my question. The target platform is Linux - and as the app will be using JACK, the Linux kernel will/should be a full Real Time Pre-emption patched Kernel (Ingo Molnár RT patches). The RT thread is provided by JACK - the app provides a callback for JACK. The callback should not call any blocking system calls: file i/o, gui code, malloc/free, printf, etc. Also recommended is a constant running time. So the period is set by JACK, and if JACK says there is work to do, my app better do it!
James Morris
There you go, then. Keep those requirements in mind and check your performance against your requirements at appropriate periods (automatically, weekly, monthly, depending on your needs). Debugging your application may be a challenge without file i/o, a gui, or screen i/o. Typically, real-time code is developed and debugged in an environment simulating its target environment. If you have something like that, use it. If not, you may need to make your own solution to push test cases into your code in a way that closely resembles your target.
thebretness
The restrictions I listed only apply to the callback my app provides for JACK to use in real time. The rest of my app can do what ever it likes ;-)
James Morris