Grab the environment for exercise 2 and refactor the code.coffee file to improve the structure and eliminate duplication. The resulting code should be shorter, easier to read, and easier to modify. The code implements an FMc16 simulator and comes with a full set of unit tests. The FMc16 instruction set architecture (ISA) is specified in fmc16.txt.
Turn in the refactored source code (code.coffee) on blackboard Exercise 2 Submission. If you modified the test.coffee file, submit it as well. If you have any questions, run into problems, or need clarification, please let me know. Your production code will be evaluated according to how you have improved the structure of the code and the amount of duplication you have removed from the original code.
You work for a DoD cyber research facility. A DoD organization has "acquired" a foreign weapon system. The weapon system is largely controlled by firmware and includes a foreign-made, embedded micro-controller your group has labeled FMc16. The client organization is sponsoring your research in order to uncover any exploitable vulnerabilities of the firmware that runs on the micro-controller. Bob, a member of you research group, has been working for the last month to reverse engineer the FMc16. He has made notes of the ISA in fmc16.txt and began to implement an FMc16 simulator. However, Bob has been promoted out of the research group and you have inherited his simulator project. Because you had previously taught Bob the merits of TDD and unit testing, all the simulator code is covered by unit tests. Before you continue to add features and functionality to the simulator (like breakpoints and a UI), you recall Martin Fowler's book on refactoring and decide to refactor the code first to improve its structure and reduce duplication. This will help you understand the code and improve its maintainability and readability for the future.
The key to easy and fast refactoring is to take it in small steps. You have a solid set of unit tests. Use them to your advantage by running them between every step. If you take big steps, you may waste a lot a time backing out of your changes because of a bug. For example, if you find duplicate code you want to move into a function, proceed as follows: write the code for the function first. Run your tests to ensure no syntax errors. Then replace the first occurrence of the duplicate code with a call to the function. Run the tests again. If they pass, proceed with replacing the other blocks of duplicate code with calls to the new function. Note, if the function is complicated or adds additional logic, it may be wise to write a test with a few test cases for the new function first, before taking the above steps to refactor.
Most of the logic of the simulator resides in the step() method of the FMc16 class. Your refactoring activities should mainly revolve around finding duplicate blocks of code and extracting blocks of code into functions to eliminate the duplication. I do not see a need to create more classes to accomplish this refactoring or use object-oriented techniques such as creating a class hierarchy through inheritance. Keep your code simple and robust.
The interface to the simulator is listed below. For the unit tests to run properly, these methods must exists and function according to their specification. As long as you don't change the behavior of the functions (only the structure) you will not have any problems. Just understand, if you delete or change the meaning of any of the interface functions, you will have to deal with fixing the broken tests as well.
The code is well commented, so you shouldn't have to dig into the details of all the bitwise operators and can instead focus on the structure of the program. However, I'm sure some of you will ask, so here are some links explaining the bitwise operators: About.com and mozilla.org.
Here is a simple example of how to move a global variable into a class that is using it. The Counter class always refers to the COUNT global variable. This is bad practice, because the state of the Counter objects is exposed. Its better to encapsulate the state with the behavior of the class. Additionally, if multiple instances of Counter are created, all instances will share the same state and thus modify the same value.
COUNT = 0 # All instances share the same state class Counter constructor: -> COUNT = 0 increment: -> COUNT = COUNT + 1 get_count: -> COUNT counter1 = new Counter counter2 = new Counter counter1.increment() # COUNT = 1 counter2.increment() # COUNT = 2 counter2.increment() # COUNT = 3 counter1.get_count() # Should be 1, but actually 3 counter2.get_count() # Should be 2, but actuall 3
By making the COUNTER an instance variable of the class---@counter, we eliminate the problems.
# COUNT = 0 # Delete this line, not needed # Wherever COUNT was referenced, replace with @count class Counter constructor: -> @count = 0 increment: -> @count = @count + 1 get_count: -> @count counter1 = new Counter # Each instance has own count variable counter2 = new Counter counter1.increment() # counter1.count = 1 counter2.increment() # counter2.count = 1 counter2.increment() # counter2.count = 2 counter1.get_count() # Correct: returns 1 counter2.get_count() # Correct: returns 2
Thoughts to consider before turning in the assignment: Waring--spoilers ahead! (I recommend taking a stab at the exercise before reading this to see what you come up with on your own; you'll probably enjoy the exercise more that way.)
Lyall Jonathan Di Trapani 18 April 2013