The importance of metrics
Metrics, or measuring the progress of a project, is essential for several reasons. Imagine taking a car trip with a three-year-old who says, "How long before we get to Grandma's house?" without knowing where you started, how far away your mother's place is, or how fast you are traveling. Well, management, customers, and even programmers are like that three-year-old. They need to know how things are progressing. While it's possible to just "dive in" to a project, it's impossible to manage it properly. You have to start with some idea of what's involved, how fast you are moving, and how much is left to do if you are going to professionally manage a software development project. This is where metrics come in. They provide a framework to evaluate your progress.
The only way to get information about the amount of work remaining is to measure progress. Unfortunately, this is a non-trivial task in object-oriented projects. Object-oriented applications grow in a non-linear fashion; they behave more like living organisms. Like trees, perhaps. They grow from the inside out and at all branches equally. This makes it difficult to measure their progress and to explain it in an easily understandable way for people who are not directly involved in the development process, like managers and customers.
But metrics not only help to plan the schedule. They also tell you about some characteristics of an application and how well object-oriented (and other) principles are applied. Are programmers using inheritance properly? Did they abstract the scenario nicely? Are they putting comments in the source code? Are the created components reusable, or are there too many dependencies that make classes inflexible?
Metrics need to be standardized to provide valuable, objective information. Metrics should not be influenced by somebody's personal coding style. If applied incorrectly, metrics can be frustrating and bad for the morale of the troops because they can spread negativism. All metrics described in this book are suggestions, not rules. Based on the type of project you are working on, some metrics might be more important than others. In general, you should be more concerned with quality metrics than with those measuring quantity. Also, only certain quantity measurements are useful for scheduling, but most of them should only be used to get a better profile of your application and further on, to find indicators for possible quality threats.
Automated measuring
Without the proper tools, it's difficult to apply object metrics not only because it's hard to count lines of code, classes and other important items, but also because the retrieved results are valuable only if they are up to date at all times. Unfortunately, I don't know of any standard tools that would help you to accomplish all necessary tasks. As mentioned above, the FoxPro world hasn't cared about metrics too much in the past. Luckily, it is not too hard to write such tools yourself. As an example, in Chapter 5 I introduced a simple Class Browser add-in that counts lines of code and other things.
To measure your application properly, you need different kinds of tools. You need tools that analyze source code to count lines of code or the number of classes. This usually is the most important kind of measuring tool you will need. However, you'll require other tools and mechanisms to accomplish special goals. To count object instances, for example, you will need a runtime mechanism that writes to a log file every time an object gets instantiated. Typically, there are no generic tools for these tasks. For the previous example, I recommend writing a simple method or function that gets called from the constructor of all of your base classes. As a parameter, I would pass the name of the instantiated class as well as the object name, and maybe some additional information such as the name of the parent object. Here is a simple class that handles instance counting:
DEFINE CLASS cInstanceCounter AS Custom
FUNCTION Init
* We ZAP our instance log table without asking
LOCAL lnOldSelect, lcOldSafety
lnOldSelect = Select()
lcOldSafety = Set("safety")
SET SAFETY OFF
SELECT 0
USE InstanceLog EXCLUSIVE
ZAP
USE
SET SAFETY &lcOldSafety
SELECT (lnOldSelect)
RETURN
ENDFUNC
FUNCTION LogInstance (lcClass, lcObject, lcParentObject)
* This method logs every instantiated class
LOCAL lnOldSelect
lnOldSelect = Select()
SELECT 0
USE InstanceLog
APPEND BLANK
REPLACE TimeStamp WITH DateTime()
REPLACE Class WITH lcClass
REPLACE Object WITH lcObject
REPLACE Parent WITH lcParentObject
USE
SELECT (lnOldSelect)
RETURN
ENDFUNC
ENDDEFINE
This class should be instantiated when the application starts typically it would be in Main.prg. The constructor of the class opens the log file and deletes all records in it. This allows you to start with a clean log file every time. If you want to count class instances over a longer period, you might not want to do that; instead, you might add some kind of start pointer so you could easily tell where the application was started when you analyze the log file. This is crucial to know, because instance counts are meaningful only when they're tied to a single run.
Of course, you wouldn't want to call this code in the release version. For this reason I suggest using the following code in every Init event of all your base classes:
IF Version(2) > 0 && 0 = runtime
oInstanceCounter.LogInstance(THIS.Class,THIS.Name,;
IIF(VarType(THIS.Parent)='O',THIS.Parent.Name,""))
ENDIF
This code checks for the Visual FoxPro version using the Version() function. If Version(2) returns 0, you know that this is the runtime version, and you don't count instances. However, there is a certain amount of overhead attached to this code. For this reason it would be better to use pre-compiler statements like so:
#DEFINE DEBUGMODE .T.
#IF DEBUGMODE oInstanceCounter.LogInstance(THIS.Class,THIS.Name,;
IIF(VarType(THIS.Parent)='O',THIS.Parent.Name,""))
#ENDIF
Usually you would define the DEBUGMODE constant in a header file to make it available throughout the application. This constant is useful in many other scenarios as well, so most likely you'll already have it defined.