When I start a new project I go through the following basic stages
- Identify what I am trying to solve: Review the requirements and ensure I know what it is I need to do.
- Review methods that I know about on how to solve it: Most problems have multiple solutions; review the pros and cons.
- Select method that fits within project constraints: Based on project constraints and the pros/cons, select the method.
- Implement: Do the work.(1)
- Refine: Based on feedback and time left, refine the approach.(2)
Today I want to talk about the refinement stage: what it is, when, and how to do it.
Before you run a marathon, have a good running form
Refinement starts once the baseline version of the project is completed, that is to say you have
- Met all the baseline requirements
- Have unit tests in place for all the baseline requirements
- Have completed integration into the larger system
If you do it poorly you will pay penalties over and over(3) but done well, there are great benefits. So what should you refine? In design there are 4 things to optimize, they are: (4)
- Clarity: Improving the design to make it easier to understand and maintain.
- Speed: Reducing the total FLOPS for both mean and edge cases!
- Memory usage: Reducing the RAM/ROM, for both mean and edge cases!
- Re-usability: Create methods for reusing the design for multiple instances.
What is most important to your project?
Items 1 through 4 are often in competition; it is easier to go faster if you use more memory but clarity may suffer if you have multiple uses. So the question becomes, “what is most important to your project as a whole?” Take into account how often the code is called and determine its priority within the execution context.
The first rule of clarity is documenting your model. Documentation should be done at a level that aids understanding. Putting comments next to each block (this is a table look-up for calculating the XXX based on YYY) provides too much information. Comment on the function of a group of blocks. The second rule is to use common patterns; do not have 6 different ways for performing the same operation.
Before you can go fast you need to know what is going slow. The first step in optimization then is to run a profiler on your system. There are two types of speed you want to examine; average and worst case. Depending on your overall system needs you may want to focus on one over the other. This can be done by implementing conditional code where a given path only executes under edge cases.
First off, elephants are a horrible storage medium; sure, you pay them in yummy peanuts but the retrieval process is a real three ring circus. Memory can be reduced in two primary methods
- Reduce what you pass in: Look at the data you are passing into your function. Is all of it needed? Frequently in the development process we “overpack.”
- Remove intermediate variables: This is less of an issue for automatic code generation. However, if you find yourself creating variables to monitor the code this will lead to memory bloat.
Remember as you refine your code, regression tests are your best friend. Consider adding performance tests as well if you are working on memory and speed issues. Follow this advice and “go for the gold!”
- While doing the work may take the most time, steps 1-3 are what makes sure that it takes the least amount of time.
- For projects that are well defined, the “refinement” may take place while in stage 4 (implementation). However, when doing something new I always make sure I have something working and solid before I start the refining stage.
- I’m not saying that is why it is re-fine, e.g. a penalty paid but…
- In computer programming this is known as “doing your bit”; if you “byte off more than you can chew.”