5

Background: My boss made a comment on porting a C program that acts as some a simulator that communicates with a remote process through sockets to Java. He didn't assign it to me, or to anyone for that matter, it was just a project he had in mind. He showed and gave me access to the C source code file for said program so I could take a look.

Now I'm an intern in this company and haven't really been assigned something in particular so I wanted to start this project on my own and then show it to my boss.

Problem: The C program is ~4k lines long. It consists on many global variables and functions that use those variables; I guess this is mainly to avoid passing many parameters. There are even some goto statements (yeah, also most of the codebase is from 1980). So basically my question is: should I aim to replicate the structure of the C program in a single Java class so there is a visible mapping of behavior between both? Or should I come up with a better object oriented design that may imply the creation of many components? What would be your approach to solving this problem?

Thanks.

  • 7
    You should ask your boss before starting working on it. Don't work on it by surprise. – Basile Starynkevitch Aug 30 '15 at 5:54
  • @BasileStarynkevitch Interesting comment. The thing is I've been in this company for 3 months now and I haven't been assigned anything yet. During the first weeks I asked for any project I could work or help in, but I was just sent to read some ISO documentation instead, which I did. So at that point I thought I would come as annoying/pushy if I kept insisting, and decided to try this out. – probablynotfranco Aug 30 '15 at 6:37
  • Ok, then do it as you want. BTW, which country are you in? – Basile Starynkevitch Aug 30 '15 at 6:38
  • 6
    Seems to me like your boss did give you an assignment - to look at the code of that C program. Since he talked about porting that program and told you to look at it it's not unreasonable to assume he wants you to port it, but considering your position as an intern you shouldn't act on such assumptions. Do come up with a design, but present it to your boss before you start investing lots of time implementing it. – Idan Arye Aug 30 '15 at 8:51
  • 1
    I would question the premise of the assignment in the first place. If its just a learning exercise, fine. If its to make the code more maintainable, wouldn't it be easier and less time consuming to iteratively improve the existing C code than write it from scratch? If its because it needs to interface with other code, wouldn't it be better to write an API? Every time you write new code from scratch you come across problems which, most likely, were already solved when the original code was written. This could end up taking tons of your time for very little benefit. – Nathan MacInnes Aug 30 '15 at 23:21
13

A 4Kline long C program is a small C program. It generally needs a few weeks or months to be written by a single person. If you are familiar with the domain of the program, you should be able to understand it entirely quite quickly, and write a tiny documentation describing the design and purpose of the original program (in particular, the communication protocol implemented by it, if that protocol is not well documented).

Big programs have millions of source lines: both GCC and the Linux Kernel have more than a dozen millions of lines of source code.

So don't think of porting that program to Java but consider instead an entire rewrite. Don't try to match some parts of the C programs to some part of your Java program. Don't replicate the structure of the C program in Java (but perhaps, use similar names in your Java code, when relevant). So indeed, come up with a better object oriented design and use the facilities given by Java (in particular its standard library and containers). But keep your code small (avoid writing 10Klines of Java if possible).

  • Solid advice. Pretty much what I was looking for. Thanks. – probablynotfranco Aug 30 '15 at 6:43
  • 3
    If it's 4K lines of C, avoid writing more than 4K lines of Java. – gnasher729 Aug 30 '15 at 10:12
  • 1
    @gnasher729 I would expect a 5 : 1 ratio when porting c to a high level programming language, 800 lines of Java (The stdlib is huge, use it) – Caridorc Aug 30 '15 at 13:12
  • 1
    @Caridorc: If there are standard-library-routines to do a big part of your work instead of having to write it from scratch where none were used before, that's reasonable. Though the shoe is on the other foot (and will pinch abominably), if that's not the case and something maps badly to Java or it's standard library. Which gets more likely the less standard and more optimized the original is. – Deduplicator Aug 30 '15 at 15:01
  • 2
    Essential reading, in case you have missed either book in your education so far: Working Effectively with Legacy Code by Michael Feathers, and Refactoring by Martin Fowler. – Jules Aug 30 '15 at 15:48
4

Like Basile said, 4K lines is a small program. It should be relatively straightforward for you to puzzle out what it does and how it does it.1

You said this was a simulator of some sort; I'm guessing it acts as a server or client to some other process for testing purposes. We have similar tools for testing our software - the simulator stands in for a remote customer and sends us canned data so we can verify our processing.

Do not try to replicate the C code's structure in Java. Rather, identify what the C program does, write up a list of functional requirements to achieve the same result, and write the Java code from scratch. If this is typical '80s-vintage C, you probably won't be able to replicate it in Java anyway.


1. Emphasis on should. Back in the early '90s we got handed a pile of C code and were asked if we could make it run faster. It was a mess - 5K lines (all in main), literally hundreds of separate variables, some declared at file scope, some local to main, gotos out the wazoo branching in every concievable direction. It took my coworker two weeks of dedicated effort to puzzle out the flow of control. We found out that we literally could not change any of the code without breaking something. As a first step to speeding it up, we tried compiling with optimizations turned on. The compiler used up all available RAM, then it used up all available swap, and eventually panicked the system.

We told the customer that we would either need to rewrite the whole thing from scratch, or they would need to buy faster hardware.

They wound up buying faster hardware.

1

It seems you want to achieve two things: Create a Java program instead of a C program, and have an improved structure. Both represent work. However, it is less work to convert a C program with a good structure to Java than a badly structured C program. And improving the structure is easier with a known working application, so you can make one improvement, test that it works, undo the work if you introduced a bug, take the improvement if it worked, and so on.

So if you do this, improve the structure of the C program first, and then convert to Java when the C code is in a good state. The problem with this conversion is that for a long time you won't have something that works, which is why you want to put the C code into a state where you know exactly how to convert each part to Java.

  • 2
    This seems like bad advice to me. Java has much better tool support for both testing and refactoring. Also good C and good Java may well have substantially different structure; the languages have different strengths and weaknesses, so agood way to approach a problem in one language could well be a bad way in the other, and vice versa. It would be much easier to approach it the other way round: convert to bad Java, write a useful test suite, refactor. – Jules Aug 30 '15 at 15:47
  • 2
    @Jules I agree, but I would have changed the order - first write an external test suite, one that operates the C program from the outside. Use the C program to verify that the test suite is correct. Then, when you have verified tests, start working on the Java code, using the test suite to check it's correctness. – Idan Arye Aug 30 '15 at 16:40
  • Jules, if you port a C program without any reasonable structure directly to Java, you will end up with a mess. You'll run into problems during the porting process, and all the time you have no working code where you can do regression tests. – gnasher729 Aug 30 '15 at 19:23
  • 1
    @gnasher729 The thing is - the amount work required for porting and structuring at the same time is considerably less than the work required for finishing one and then doing the other. If you port first, you'd have to write procedural code in Java. If you restructure first, you'd have to write OOP code in C. Both are possible, but very unidiomic, and you'd have to struggle with the language. Going through that for the dubious benefit of being able to do the porting almost line-by-line in a single go before you start checking the new program is simply not worth it. – Idan Arye Aug 31 '15 at 11:05
  • Also, you don't have to wait until the new program is completed before you start testing it. You can do the porting feature by feature, and test every feature after it's done. The C program wasn't written in one go either! – Idan Arye Aug 31 '15 at 11:08
1

I see no reason to port the code from a procedural language to an object oriented language without making the code object oriented. Do you just wish to be able to write procedural code in an object oriented language?

Object oriented code is a step forward. Procedural code in an object oriented language is for the least not forward if not exclusively backwards. With the exercise that you saying that you are going to do, at the most you are going to learn How to change a code with GOTOs to a code without them.

IMHO, try to understand the purpose of that procedural code and then design it in terms of Classes. Make use of the four principles of Object Oriented Design - Abstraction, Polymorphism, Inheritance and Encapsulation. Learn about and bring into your practice the SOLID principles. Learn also how to test your code.

Designing an object oriented system is far more fundamental than coding.

1

Naturally, the correct procedure would be to do it the right way, Java is an OO language after all, but whether you should mimic the current coding style or do it the right way an OO programmer would is not up to you, but up to your lead.

Therefore discuss with your manager first, if they want to keep the structure as is, or make the code more agile friendly, introducing design patterns, the SOLID principles,...

1

Do it both ways.

  1. Make one new Java class to match as closely to the original C as you can, using static variables in the same way the C code does. In most cases do a direct one-to-one translate of each line of code. Even use the same loop types, parameter names, method names and variable names and even keep the gotos.
  2. Test that code to the maximum possible, comparing it against the C version in every way you can.
  3. Write tests for the new code that tests all of the functionality you can. Ideally testing the metrics you used in 2 above.
  4. Take a copy of that class.
  5. Refactor it into objects using patterns, interfaces and a good OOP design. Retain notes on every refactor, especially the gotos, trying to prove that your refactor cannot change the final result.
  6. Test the new class with the old tests written in 3 above.
  7. Repeat from 5 until satisfied.

With this approach you should achieve not only a perfect clone of the original code but it should also be both testably (from your tests) and provably (from your notes) correct.

0

I've dealt with a lot of these 80s C "vintage" codebases. If you want to hear some horror stories, I've dealt with ones where a single function was bigger than the entire codebase you're talking about (single functions that spanned over 20,000 lines of code with about 30 levels of indentation using a one-space indentation style and 50 or so variables declared at the top).

Even though your codebase is so teeny in size, I recommend approaching it from a thorough test-oriented perspective if the code is really complex in a way where you kind of need to reverse engineer it. Of course if you feel like you can reconstruct the program in Java without this kind of reverse engineering process, this might be a case where it's excusable to kind of start a new canvas from scratch.

If it is complex though and you don't feel confident that you can replicate the original software's functionality easily then...

Wrap In C to Desired Interface

Even C allows object-oriented programming. You can model objects like this (C++):

class Foo
{
public:
   Foo();
   ~Foo();
   void do_something(int blah);

private:
    float x;
    int y;
};

... in C like so:

// In header file:
struct Foo* foo_create();
void foo_destroy(struct Foo* foo);
void foo_do_something(struct Foo* foo, int blah);

// In source file:
struct Foo
{
    float y;
    int x;
};
...

... there are even ways to emulate inheritance and polymorphism through composition and function pointers.

With this strategy, you can start extracting C interfaces over this messy blob of code which are well-designed, conform to SOLID principles, etc.

I recommend this strongly over the temptation to refactor the code directly where the iterations might start to seem endless as you get knee-deep in implementation details while simultaneously trying to find a sane structure out of it. Boldly create wrapper interfaces instead which model your final Java interface goals right from the start.

You could also potentially wrap this code into C++ classes given the ease of interoperability between C and C++ which might make it even easier to port this to Java. The initial versions might be stateless and just call functions in the C codebase if they're based on a bunch of globals, but don't let that impact your interface designs. Design them like they have encapsulated state.

Focus on getting the public interfaces right. You can do all kinds of inefficient trickery under the hood to make these interfaces work against this convoluted codebase. The initial goal here is to reshape the design and structure, not implementation (that comes last).

Write Tests for New Wrappers

Now write tests for these wrapper interfaces you construct, making sure they conform to your interface requirements. You also want to apply possibly a more integrated mindset here testing for correct output. The process of constructing these tests and wrapper interfaces will also rapidly accelerate your understanding of how the legacy code works.

Keep going until you've covered the entire codebase and established an acceptable structure, translating things on the way. The result should be a public interface which is suitable to you. Then start porting the client code in your codebase to start utilizing these interfaces.

Port to Java

Last but not least, port this to Java and replicate the same interfaces and tests. At this point, you should have a good enough understanding to fill in the implementations.

This is a very long and exhaustive route to solving the problem, but it shouldn't take that long given the teeny size of this codebase. I ended up doing this kind of stuff for millions upon millions of lines of code, so this should seem like a vacation in comparison.

Alternate Approach

An alternate approach mentioned already is just start off from the Java side. The reason I recommend starting off wrapping and testing from the C side is that I'm assuming the value of this convoluted piece of code relates to the outputs it provides over a given set of inputs. It can increase the rate at which you learn and understand the program by wrapping and testing it initially in the same language it was originally written, albeit very roundabout, and also makes sure your tests don't miss some subtle behavior in the original which might need to be preserved.

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.