CSC-430

Software Construction

Get It To Compile

When completing assignments for this course, you can greatly improve your odds of success and reach a solution much faster if you use the right strategy. Many aspects of this strategy will also be useful for developing code in general.

Make Sure You Know What You’re Implementing

The first thing you should do when beginning to work on a project is read the instructions. Reading the entire README document will give you the context of the individual steps. Once you have a high level understanding of what is being built and how the various pieces interact, it is much easier to work on the individual, isolated components of the code.

In the real world, you will rarely ever have actual instructions, but you will obtain similar benefits by making sure you have reviewed and understood any provided design documents, specifications, and other documentation. If such documents do not exist, sitting down and creating them before you start coding will help you to ensure your plan is clear and that your code will be headed in the right direction before you start typing.

10 hours of coding can save 1 hour of planning

Without the proper context, you will inevitably waste time writing code that doesn’t make sense and gets you no closer to a solution.

Be Competent Enough To Implement It

This may seem difficult to change, and to some extent, the skills you have are the skills you have. Many of you, though, run into problems, because you chose not to do the course readings and watch the lectures. This is a competence problem that can easily be fixed. More generally, though, investing time in improving your skills will lead to significant increases in your ability to write code quickly and correctly. If you refuse to invest in your skills at all, you simply won’t be able to do this for a living and will be wasting your time and money taking courses.

Get Your Code To Compile Before Writing Any Other Code

You are provided with unit tests and skeleton classes that you need to complete. If you attempt to complete portions of these classes without getting your code to compile, you will not be able to get feedback about whether or not any of your work is correct, because the tests can’t be executed. You may also get confusing errors from your IDE, because the code is too broken for it to make sense of your work.

Accordingly, after you have read the instructions, the absolute first thing you should do is get all of your code to compile. Once you have accomplished this, you can execute your tests and use them as an additional guide for completing your solutions.

Also, since you will receive a zero for projects that do not compile, this at least gets you started down the right path for getting some score. It’s not uncommon for students to write a lot of code that is correct, but doesn’t compile, because of other pieces of code and they will receive the same grade as if they had submitted nothing at all.

Fortunately, there are many common issues that you will face that you can easily resolve to get your code compiling.

Add Your Dependencies

Nothing will compile if you do not have all of the code available that your code depends on. Make sure that you properly configure your pom.xml file to add the course repository and the individual dependencies as described in the instructions. Make sure that you reload the pom.xml file and run compile using Maven at least once to ensure that the dependencies have been downloaded. You can confirm that the dependencies have been downloaded by expanding the External Libraries folder in the project pane of your IDE.

Common Pitfall: Make sure that you are using the proper version of the course dependency!

Common Pitfall: Be careful with your dependency scope. The safest thing for you to do would be to not include a scope when adding your dependencies. In general, though, you will use no scope for dependencies in the code you will release (the code in src/main and will add the test scope to dependencies only used by tests code in src/test.

Make Sure All Interfaces Are Implemented

The compiler will struggle to process your code if you haven’t properly implemented the proper interfaces in your code. Similarly, you need to make sure you have extended any parent classes that are required.

This involves a couple important steps. First, you need to modify the provided class files so that they reference the interfaces and parent classes. For example:

public class SimplePoint implements Point {
  // ...
}

or

public class SimpleBlob extends BaseBlob {
  // ...
}

This tells the compiler what behavior you are agreeing to implement in your classes, or what methods and fields you are inheriting.

The second step is to actually provide some implementation for any methods you have agreed to implement in the first step. Remember that we are only concerned, at this point, with getting your code to compile, even if the implementation is wrong. In other words, we just need enough of an implementation to achieve compilation.

Non Void Methods

For example, if we implement the Point interface, we are agreeing to implement a move method.

The simplest implementation that we can provide is:

public Point move(final int dx, final int dy){
  return null;
}

This is clearly not correct, but it will compile. Personally, I would recommend doing the following instead:

public Point move(final int dx, final int dy){
  throw new UnsupportedOperationException("move not implemented");
}

The benefit of this being that it will be more obvious why the code is failing than subtly introducing a null value into you code.

Void Methods

For void methods, this is even simpler, because you don’t need to provide anything in the method body at all!

public void someVoidMethod(){
  // this compiles just fine!
}

Though, again, I would recommend:

public void someVoidMethod(){
  throw new UnsupportedOperationException("someVoidMethod not implemented");
}

Constructors

Constructors can be a little trickier, because there may be some instructions required, though, in many cases, you may be able to just leave the body empty. One issue you may encounter is that some final class field may need to be initialized. In this case, you will need to ensure that the field is set in the constructor before the code will compile. Another issue that you may encounter is when there is a parent class which does not provide a default constructor. In such cases, you will need to add a call to the super constructor.

Pro-tip: When you specify that your class extends another class or implements some interface, your IDE will likely tell you that you have an error because there are unimplemented methods and will usually offer to provide the necessary, temporary method stubs for you.

Common Pitfall: If things aren’t going smoothly, make sure that you have imported all of the necessary classes!

Pro-tip: Your IDE will typically make recommendations for imports and offer to add the necessary statements for you. Let it, but make sure the imports are actually the correct classes!

Common Pitfall: Your IDE can’t recommend any imports if you haven’t properly added your dependencies!

Execute Tests Frequently

At this point, your code will most likely be compiling, but, to make sure, you should now run maven test to ensure that your code can compile including your test code! This will help to ensure that you have actually properly extended the proper classes and implemented the proper interfaces.

Once we know we are able to run our tests, we will be able to focus on implementing individual methods and getting feedback on their correctness as we progress. The basic idea is that you should run the tests, read the next steps in the instructions, attempt to implement a method, then run the tests again to see if you have fixed any of the failing tests.

Common Pitfall: In a thoroughly tested code base, every method you implement should fix at least some tests, but I will be providing fewer and fewer test throughout the semester, so it’s possible that this won’t be the case and that you’ll just need to move on when you’re confident things are fixed (or add a test for it yourself!).

Repeat this process of implementing and testing (following, roughly, the order of the instructions), until everything passes!

Common Pitfall: It is convenient to right click on tests or test classes to execute unit tests, but this may not use Maven and could give you slightly misleading results. It’s fine to do this, but before submission, make sure you actually run Maven test to confirm that everything actually builds and passes the tests.

Use Your Build Server

Once you are confident your code builds and is correct, you should commit and push your code back to GitHub and then check Circle CI to see if your code builds and passes tests for everyone else who has your code. If you forget a class, your code doesn’t compile, and you get a zero!

Use The Compiler As A Guide

Another important skill to learn (at least when using a strongly typed programming language), which will help you with the above steps, is to let the compiler and unit tests guide you when writing code. There are infinitely many methods that you could write, but when solving a particular part of your solution, almost all of them can not possibly be right, and the compiler will often tell you when you have written such a method.

For example, if there is a unit test that includes the following:

final Point p = new SimplePoint(10,3);

And you are also given the class skeleton:

public class SimplePoint {
  public SimplePoint(){

  }
}

Then you’re immediately going to be given an error indicating that the compiler expected a value of type Point, but was actually a SimplePoint. This is telling you, basically, that SimplePoint must be a Point. In other words, SimplePoint must implement the Point interface.

public class SimplePoint implements Point {
  public SimplePoint(){

  }
}

Now, we have two additional errors. In the test file, you will be told that there is not a suitable constructor for the expression new SimplePoint(10,3). In other words, you need to add the appropriate parameters to the constructor:

public class SimplePoint implements Point {
  public SimplePoint(final int dx, final int dy){
  
  }
}

In theory, we don’t know whether the dx or dy parameter comes first. This is where you, as a human, need to think about what is reasonable (for example, in mathematical coordinates, x usually comes before y). Of course, you could still be wrong, but since we have unit tests which acts as a kind of specification, you can execute the tests and switch the order if they fail (after, of course, you have provided a real implementation)!

At this point, the test code should be compiling, but you will now be given an error about not having all of the methods in the Point interface implemented. As mentioned above, your IDE will gladly stub the methods out for you if you ask it to.

Of course, there are other problems you’ll encounter, but the basic point is the same: the existing code and the unit testes will typically provide all of the information you need to get your code compiling, though you may have to work through the errors and reason about them. Just take one error, think about the changes it infers, fix it, then recompile and repeat.

Be Patient And Methodical

While this article doesn’t give you a solution to all issues you’ll encounter, the underlying theory is always the same: focus on a single step at a time, think about any errors or other feedback you have, and resolve the issue before moving on. The worse thing you can do is to clone your skeleton then start jumping around to different files trying to frantically get things done with no process or feedback. You must approach things methodically and patiently reason through your problems.

Common Pitfall: This is not possible if you wait until the last minute to do your work, so start early and solve small bits of the project at a time.

Pro-Tip: If you start early and get blocked, you have plenty of time to ask me questions instead of sending me a flurry of emails at 11PM!