Saturday, July 3, 2010

Software Design

Hey guys,

So, if you've taken a look at droidViz lately, its a bit of a mess. "fluid.c" is all over the place with functions for creating and rendering particles, as well as allocation of density arrays and velocity arrays... The file is too long and difficult to dive into.

I thought I'd do some thinking out loud "in blog form" and talk about good and extensible software design for a bit.

Currently, droidViz is written in straight C. This code isn't object oriented, but it doesn't really have to be. All it has to be is organized and easy to read. Unfortunately, I've not had much practice with non object oriented languages and software, so much of my experience is with OOP. I'll be beginning a transition from the current droidViz.c into C++. (Yeah, I know C++ isn't TRUE OOP, but that's really just nitpicking...)

So, what goes through my head when designing a software framework? Well, as far as I can gather, this isn't really taught in school and is something you really need to pick up on your own. I find that easily half of my time programming is spent thinking about how the next feature will be best implemented into a framework.

So, now that we're talking OOP, we have classes. What is a class for? A class is a definition for an object. A class may be instantiated elsewhere to easily manage something, or inherited by a subclass for further specification. When I start thinking about the program, I think about classes as building blocks. I start with a base class which performs one or two tasks and performs them well and transparently, then instantiate the class as an object in another class, used only to perform its job. Doing so makes code more readable and easier to debug. If we can confirm that the base class is performing without error, we can assume it does everywhere else and move on.

So what needs to be put into a class and what doesn't. This is a tricky question, especially when dealing with the JNI. See, we can make as much of our code as modular and objective as we want, but in the end it becomes non object oriented when we create the JNI interface. We package this JNI interface into a Java object, but at that transition layer, our nicely object oriented code just becomes a static library which instantiates the main class and calls its member functions.

So, let's look at an example of a signal processing chain. I know it has little to do with droidViz, but just bear with me. It'll explain a lot later. I need to apply an IIR filter and a Low pass filter to my incoming data. How would I do this? It shouldn't be a function, because this code will likely be long and not have much to do with the remainder of the code in the current file. Well, I'll first make a class called "CustomFilter" and create an initialize function, and a process function. In Initialize, we'll set up the variables and allocate space for the data, and in process we'll perform the IIRFilter processing and the LPFilter processing code, and output it. Then, later in my code I can create an object of the "CustomFilter" class and use it.

Is this an intelligent way of setting up this code? It depends. If this is the only place that this sort of code will be needed, sure this is sufficient. If we'll need more filters down the line - even one more - and that filter will need special configuration which is different from the first filter, this is not a good way to structure your code.

Usually I only put code which logically falls into objects into classes. If I have a generic signal processing chain, I'll declare a "Filter" class which has methods that all filters will need - like Initialize(), and Process(), etc. I'll then derive several classes from this "base" "Filter" class - like IIRFilter, LPFilter, and FFTFilter. Polymorphic inheritance comes in handy here because when I create the "main" class "FilterChain", I can have an array of "Filter" objects which all have the same interface; though they perform different tasks. Filter chain will pass the output of one filter to the input of the next, automatically. This way, I don't need to specifically handle this in my code where I'm using the filter - adding unnecessary complexity and unreadability at a higher level of code.

In the end, when I need to implement a signal processing chain which performs a high pass filtering algorithm, its as easy as instantiating a "FilterChain" object, then an "IIRFilter" object. I can then use methods custom to the IIRFilter class to customize this filter to my needs, then I can add the IIRFilter object to the processing chain with a simple "FilterChain.AddFilter(filter)" function. I can do the same to add an LPFilter object to the chain. Now whenever I get data, I can pass it into the "FilterChain.process( data )" method and get an output.

Here's where the beauty and elegance of the design lies: I don't have to worry about the details of the processing at this point. I could hand this code off to someone with no DSP knowledge, and they'd be able to use it and understand what the code is doing. So long as the processing code is performing its function successfully without crashing, they'll never even need to see the filtering code - and just assume that its okay - allowing them to focus on matters they understand without distracting them or overwhelming them. Furthermore, so long as I know that IIRFilter performs its task well, and that LPFilter successfully performs the low pass filter, and that FilterChain feeds the output of the first filter in its stack to the input of the next filter in its stack, I know it works fine. Debugging becomes easier at this point because I can simply isolate where the error is happening and work at that level.

Of course, there is a speed trade off with levels of abstraction - especially when this abstraction hits the JNI layer. Not everything can be neatly packaged and still operate well - especially in this world of embedded devices and JNI. This presents an interesting problem for me, because I'm so entrenched in this modular design which works so well on our computers.

droidViz is no signal processing chain, so a lot of what I said here is irrelevant to the project, but its good advice to follow when designing your software. I'll be thinking aloud and figuring out "base" and "main" classes of droidViz in the next post. Keep your eyes peeled.

-Griff

No comments:

Post a Comment