1

Is there a known method or pattern to implement a simple controller for an MVC design in pure C or the switch case approach is the standard?

Background :

I have an embedded application and I'm pleased with how the business logic turned out. I also finished the UI which boils down to basically 2 functions:

uint8_t getInput(void);
void display(logger toDestination, char* message, ...)

Inputs can come from multiple sources and outputs can be sent to several destinations. All of that is taken care of so the application has no idea if its run by a PC on on the device. It also has no idea if the events come from humans or are faked by unit tests.

Where I struggle is with the application logic. My views are in charge of where and how to display the outputs. My drivers, regrouped into behavioral models providing clear features, know how to do their jobs very well but not when.

Here's my functional rough draft, which in my opinion violates principles like DRY, cohesion, flexibility and may present scalability problems:

Rough draft : (I faked the names and reduced the size but you get the idea)

menu.h

void goToMainMenu(void); // This is the entry point of the application after everything was setup correctly

    void goToFirstSubMenu(void);
        void firstFunctionality(void); //The last levels are the ones doing the calls to the business logic
        void secondFunctionality(void);
        //Other functionality or nested sub-sub-menus

    void goToSecondSubMenu(void);
        void thirdFunctionality(void);

    int doSomethingWithTheBusinessLogic(int arg1, char arg2, void* arg3);

    //Other sub-menus and options

    /* And so on, the function prototype are indented to reflect the logical level of the menus.*/

Then most of the functions will roughly look like this:

menu.c

void goToMainMenu(void)
{
  uint8_t choice = 0;
  resetDefaultState(); 

  do
  {
    display(toTerminal, "Press 1 to go to sub-menu 1" ENDL);
    display(toTerminal, "Press 2 to go to sub-menu 2" ENDL);
    display(toTerminal, "Press 3 to do something with the drivers" ENDL);
    display(toTerminal, "Press z to go back." ENDL);

    choice = getChar(); /* Blocking thread waiting for events to pop from the other threads */

    if     (choice == '1'){ goToFirstSubMenu(); }
    else if(choice == '2'){ goToSecondSubMenu(); }
    else if(choice == '3'){ doSomethingWithTheBusinessLogic(); }

  }while(choice != 'z');

  resetDefaultState(); 
  /* When a submenu returns we're taken to the parent menu's do-while loop */
}
  • Is it a good way to do things for small-medium systems?
  • How could this be improved considering the limits of C and embedded environnements?

What has been tried :

I tried a n-ary tree approach where nodes are menus and leaves are functionality but without polymorphism it's hard to define elegently how both should behave to inputs. The strongly typed system also makes it hard to do a simple callbacks system because I end up needing tons of wrappers around the drivers to unifiy a single callback signature (void myCallback(void*)).

It also seem to be overkill for this simple state-flow; I feel like I'm working against KISS and productivity only because I've been hammered with "chains of if-elses is the devil".

What I would like :

With higher level languages usually you can bind events and callbacks. Something along the lines of bind(myMenu, thisInput, theCallback) so whenever myMenu is focused and an event thisInput it raised theCallback is called.

I'm unsure how to do this in C without going way overboard.

  • What are you uncomfortable with specifically? The if else statements? It's basically a command line interpreter; if you want something more sophisticated, you could use a command line parser. But there's no getting around mapping user commands to methods. – Robert Harvey Aug 5 '14 at 16:16
  • 2
    Is the embedded system going to be modified so often that this extra framework is necessary instead of just hard coding prompts and responses? Usually embedded systems are crafted for one, unique, long-lasting role, making high level designs like this ultimately time wasting. YMMV of course. – Ampt Aug 5 '14 at 16:18
  • Everything feels so repeated, inflexible and hardcoded. I'm writting what feels like the same logic over and over again with minimal adjustments on how the hardware has to react. – Asics Aug 5 '14 at 16:20
  • How much future flexibility are you going to need? Does it have to be configurable? – Robert Harvey Aug 5 '14 at 16:20
  • @Robert It's hard to tell. My internship is finishing in 3 weeks and my supervisor will take what's left to do from there. The specifications are pretty clear but the requirements still change every other week meaning I have adapt quickly and my supervisor will have to dig into my code. I want to be proud of my final product not have them go "wtf did the intern do again". – Asics Aug 5 '14 at 16:23
2

There is a development process question which should be understood and answered before worrying too much about the code.

IMHO, any organisation which employes more than one software developer, and especially one using interns to develop code, should have sane, rational, understood, shared development processes. There is a tremendous amount of evidence that reviewing code helps a lot. They help all developers to get onto the same page about coding techniques, style, libraries, tools, etc.

Recommendation 1: Ask your supervisor about doing a review of your code or design as soon as practical. It'll give you some feedback from people who's views are much more relevant than ours. Also it'll provide a way for your supervisor to understand your code well in advance of you leaving, with a clear invitation to direct you to fix things they don't like.

Let's try to look at it from some other persons position. They have to change the code, without breaking it, or fix a bug. They might be the next intern, with less knowledge than you. They want life to be straightforward. Ideally they will look at your code and say to their supervisor "this code is great, I can understand it really easily, and I am confident the set of test cases they built will catch most of the types of errors that I might make."

When I have to read other peoples code, I like it 'as simple as possible, but no simpler'.

The less code I have to read, the fewer ideas, and the less 'stacking' or 'cross-referencing' (I wonder how that works, I'll have to go and read that before I can progress), the better. So callbacks are unwelcome unless they reduce the total amount of effort a lot. Big libraries, with lots of extra features mainly obscure the code. Good unit tests and test cases help me to understand what the developer intended.

Your code seems like a model of how this might be done. My only slight criticism is:

    display(toTerminal, "Press 2 to go to sub-menu 2" ENDL);

then later

    else if(choice == '2'){ goToSecondSubMenu(); }

requires someone to ensure the text of the terminal message, and the value in the if test matches. If the program really is that level of complexity. I wouldn't worry. Hoever if it is significantly larger, then I might be tempted to create some named constants, and write something like:

#define SUB_MENU_2 '2'  
...

    display(toTerminal, "Press " SUB_MENU_2 " to go to sub-menu 2" ENDL);

    else if(choice == SUB_MENU_2){ goToSecondSubMenu(); }

You might be tempted to put the message and choice character into an array of structs, so that the choice value and message are kept together. That is making the reader of your code have to think a bit harder, but you might find it helps if there are some other requirements. For example, it might help to identify values explicitly, so that more ways of navigation can be made to work, e.g. driving up and down the menu with cursor keys, or pressing the digit. However, resist the temptation to write 'clever' code; it causes a new reader to have to move around the code, rather than simply read it in sequence.

MVC is useful when you need to separate concerns when one or more parts are complex, or there is a lot of value in reusing solutions to part of the overall problem.

For example, the code to manage a view, menus, mouse clicks, and keyboard input in a windowing system is complex to write and test. It is worth reusing that. Your 'view library' is display() and getchar(). That's it. The rest is your code to do more view stuff.

There seems to be very little control state. In the example you've shown, the view isn't changing what is offered to the user depending on the state of the model or rules managed by the controller. Further, the actions seem to be straightforward too. So the controller seems very simple, and the model encapsulated.

If that changes, and the view must dynamically respond to reflect the state of the model, or their are several ways to get at the model, or more 'input validation' of data, their may be a need for a more explicit implementation of MVC.

The stuff you have been taught is typically designed for very large systems (or is just another new fad). Unfortunately, when it is taught, the reasoning or rationale might be poorly explained.

IMHO, some of that complexity comes from people trying to implement things in a language which is not a good fit. For example, SmallTalk, the original home of MVC, makes some things easy because it is so dynamic and rich, however some of its mechanisms are ugly to implement in other languages. Hence we end up with a lot of complication, to address complex and very general situations.

Also, there is quite a lot of money, marketing and ego tied up in offering very clever solutions designed for extremely complex problems, then selling the skills capable of applying those clever solutions. It is unlikely you'll hear a large system integrator say "no, you don't need anything this clever and expensive, you just need that very simple, straightforward approach that any competent developer will use successfully". Just my $0.02.

  • I wholeheartedly agree with the first recommendation. The reason I turned myself to stackexchange is that my situation is a bit original. I finished what was planned for my internship just before my supervisor left on vacations so they basically told me "You know the product; you have 3 weeks to impress us." This is why I'm seeking insight here even if I understand this is a Q&A board and not suited for discussions. Thanks for your help. – Asics Aug 5 '14 at 17:57
  • @Asics - I have been responsible for hundreds of developers, so my views may be very different. I would be impressed by someone who ensures that a product has been reviewed, well tested, with automated builds, light-weight documentation about how to build, test and install it, with good source code control, and that has been handed over to someone assigned to maintain and support the product. Further, if I ran code complexity tools across it, the score would be "very easy to understand", and when I run static or dynamic tools across it, it would have no defects. – gbulmer Aug 5 '14 at 18:18
  • Again thanks for your help. I ran cppcheck and it found nothing. I also ran a cyclomatic complexity tool (cmm) and over 30 source files the longest function is 25 lines long and the worst complexity is 6. Any other tool you could point me to would be greatly appreciated. – Asics Aug 5 '14 at 18:53
  • 1
    @Asics - I think that is pretty good evidence to include with your app source. I like to see code and all of its infrastructure, such as automated-build scripts, in a source code control system. I'd like a short, simple set of instructions (maybe a README file), that would ensure I could go to a completely clean machine, get the README, do what it says, and get a working system. I am impressed by people who deliver working systems, that are simple to understand, build, install and use. The program code is a big part, but it isn't everything, and IMHO the non-code part is just as important. – gbulmer Aug 5 '14 at 23:58
2

To take a step back, I think that you may be over-complicating things with your design. You mention an MVC pattern at the beginning, so I'm assuming that you're knowledgeable about web-frameworks and high level designs.

You should neatly tuck that away and put it in your pocket.

When working on an embedded system, you are making some serious tradeoffs between abstraction and complexity. On one hand, you could create well crafted interfaces for each component you use, and each microprocessor would have a different implementation for that, and you'd live in this beautiful, neatly coded world where you never actually did anything useful, and if you did, it would take 13 different interfaces, which worked for 300 chips and modules you never actually intended to use, and when you tried to program it you would realize the chip you targeted isn't big enough.

On the other hand, you could decide that there is value in simply having a working device, and that simplicity will help in debugging an already error-prone system. Between chip errata, incorrect spec sheets, and the wide array of possible chips you could use, tracking down why your serial port doesn't actually communicate to your LCD can be a nightmare even without extra interfaces on top of it.

It may seem counter-intuitive with the high-level work you've done previously, but it's simply a way of life when working on an embedded system. Extra complexity means extra bugs, and you can't always update the system once it's out in the real world.

  • I think I agree with what you are saying. I'm a student diving into the world of embedded sytems and it seems like everything I've been taught in school turns out to be pretty on paper but cumbersome in the real world.. – Asics Aug 5 '14 at 16:31
  • 2
    @Asics: A lot of what you were taught in school applies to big systems. – Robert Harvey Aug 5 '14 at 16:33
  • @Asics and what you learn here with embedded systems will apply to everything, except using particular frameworks - but you won't miss them. – gbjbaanb Aug 5 '14 at 17:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.