Xcode is the preferred development suite for MacOS X and iOS projects. This suite provides the tools we need to build software products using the Cocoa framework and Objective-C.
For instance, we can lay out a product's user interface with the Interface Builder tool. We can use the MallocDebug tool to locate the source of memory leaks and null references. We can create and manage source repositories with Subversion or Git. And naturally, we can use the Xcode editor to prepare and compile a project's source files into an executable binary.
As an IDE, Xcode is far from perfect or complete. For instance, it cannot analyze a given project in terms of cost. We can address this lack with the help of Python and Xcode's script system. This article demonstrates how to implement several code metrics in Xcode. And it shows how a typical OS X and iOS project fare against these metrics.
Readers need working knowledge of both Xcode and Python.
The Issue of Cost
In today's austere economy, software projects must achieve their design goals with a minimum waste. This means project managers should check whether their projects are within schedule and are progressing smoothly. They should know if one project needs more resources or if another needs to be scaled back.
To achieve these aims, managers could analyze their projects with cost metrics. These metrics are empirical tools that rate a project in terms of expended time. The results are expressed in terms of person-times but can be expressed in monetary costs when combined with salary rates.
Cost metrics help managers better estimate the budgets for current and future projects. They help form competitive, more realistic contract bids with certain customers. They can even help make the right informal decisions for guiding a project's growth.
Now most cost metrics have these common traits:
- use of empirical constants to describe a project type.
- reliance on a precise measure of project size.
- ability to compare two or more projects of the same type.
- allow for calibration, using data gathered from previous or similar projects.
It is, of course, good practice to rate a project with two or more metrics. This helps ensure an accurate analysis, and it helps corroborate results.
Scripting by Xcode
One interesting feature of Xcode is its support for scripts supplied by the users themselves. One script may create a build phase, one may use a different compiler or linker. Another may alter a copy phase, adding new files to the final binary or loading the binary to a server for distribution. And then there are the utility scripts.
Xcode lists all its utility scripts in its Script menu (Figure 1). A utility script can affect the active source window or it can affect the entire project. To invoke a script, users can simply select the script's name from the menu. Alternatively, they can use a key-combo if one is assigned to the script.
Utility scripts can be written in most shell languages like bash, Perl, and Python. Some scripts can be written as Automator workflows, which can interact with scriptable Cocoa applications. Some can even make AppleScript calls with the aid of the
To view and edit a utility script, choose Edit User Scripts from the Script menu. This displays the script editor window (Figure 2), on which are two major panes.
To the left is a list pane. It shows how each utility script is arranged on the menu. It also allows us to reposition a script by selecting and dragging its name. Below the list pane is a button toolbar. We use this to start a new script or group, to remove a script, and to import a script from an external file.
To the right of the editor window is the editing pane. It is where we can view and alter a script. It is also where we can change the input and output nodes for a given script.
Details on using the Xcode script editor are simply beyond the scope of this article. Readers who want to write their own utility scripts should read the official XCode user guide from Apple.
A Measure of Size
As mentioned earlier, cost metrics need a precise and reliable estimate of project size. One way we can obtain this estimate is to count the number of lines contained in each project file. This is the "SLOC" metric (SLOC is short for source lines of code). As a rule, large projects, those with thousands of lines of code, take more time to develop and maintain. In contrast, smaller projects, with hundreds of lines or less, take less time.
SLOC metrics come in several forms. One form, xSLOC, counts the number of executable lines in a given project file. These are lines that are compiled into executable code and assembled into the final binary. Another form, dSLOC, counts just the white-spaces and comments that make up each file. It measure the overall readability of the given file.
But the form we are interested here is eSLOC. This one counts the number of effective lines of source in each file. An effective source line is one that compiles into executable code. So, this excludes white-spaces, as well as comments. An effective line is also one that forms the final product. Header statements are excluded because they are just file references. Debug, assert, and trace statements are excluded because they are meant for testing. And pragma statements are excluded because they control the source editor and compiler.
Listing One shows an Xcode utility script that measures the eSLOC of the active source file. It uses the
sys module to establish its
stdin port. Plus, it assumes that the source language is Objective-C.
#!/usr/bin/python # -*- coding: utf-8 -*- # # import the following modules import sys # read the source lines from STDIN tBuf = sys.stdin.readlines() # start parsing tLOC = 0 tBgn = False for tLin in tBuf: # filter out whitespaces tTst = tLin.strip() tLen = len(tTst) if (tLen > 0): # filter out inline comments tMrk = tTst.find("//", 0) if (tMrk != 0): tMrk = tTst.find("/*", 0) tMrk = tMrk + tTst.find("*/", 0) if (tMrk <= 0): # filter out import statements tMrk = tTst.find("#import", 0) if (tMrk != 0): # filter out block comments if (tBgn): # check for a closing token tMrk = tTst.find("*/", 0) tBgn = (tMrk == -1) else: # check for an opening token tMrk = tTst.find("/*", 0) tBgn = (tMrk >= 0) if (not tBgn): tLOC += 1 # print the metric results print "Effective Lines of Code: %d" % tLOC
The script begins by reading all the lines from the source file, storing the lines into the array buffer
tBuf (line 8). As it parses the buffer, the script looks for lines that start with the token
'//', or those that start with the token
'/*', and ens with
'*/'. It skips these lines because they are inline comments.
Next, the script looks for lines that start with the token
#import. These are, of course, the import statements, and thus are skipped over. Then the script looks for a line that start with a
'/*' token but does not end with a
'*/'. If one is found, the script skips this line and the rest untill it finds one ending with a
'*/'. These skipped lines form the basic block comment.
Finally, the script counts those lines that it did not skip. It prints the total count, which is the file's eSLOC, to be viewed by the user.
Note that the script does not look for lines with
#pragma, test keywords, and other "unwanted" keywords. Readers are welcome to modify this script if they want to filter out those keywords.