The Personal Software Process: an Independent Study | ||
---|---|---|
Prev | Chapter 3. Lesson 3: Planning: Estimating software Size | Next |
Write a program to count total program LOC, the total LOC in each class the progra contains, and the number of methods in each class.
Requirements: Produce a single LOC count for an entire source program file and separate LOC and method counts for each [class]. Print out each [class] name together with its LOC and method count. Also print out the total program LOC count... Use the counting standard produced by report exercise R1. It is acceptable to enhance program 2A or to reuse some of its methods, procedures, or functions in developing program 3a. Testing: Thoroughly test the program. At a minimum, test the program by counting the total program and [class] LOC in [programs 1A, 2A, and 3A]. Include in your test report a table giving the counts obtained with program 2A and 3A for all the programs wirtten to date. | ||
--From [Humphrey95] |
Table 3-1. Test Results Format -- Program 3A
Program Number | [Class] Name | Number of Methods | [Class] LOC | Total Program LOC |
1A | ABC | 3 | 86 | |
DEF | 2 | 8 | ||
GHI | 4 | 92 | ||
212 | ||||
2A | ... |
The phrasing of some of the requirements somewhat implies that the program is expecting one source file for the entire program; our coding standard dictates that a separate source code file be used for each class. This can be easily dealt with by keeping our philosophy of parsing standard input, and funneling all files into the program using a unix pipe (see the requirements for program 2a for an explanation).
Of course, since we have decided in the coding standards to split each class into a separate source file, it's tempting to use the trivial implementation-- run program 2a once per source file to count the LOC in a class! However, this not only means more work in usage, but also defeats the purpose of writing another program using PSP 0.1. As a result, we'll parse the entire program on standard input. No checking for validity will be done. LOC will be counted as it was in program 2a.
For C++ code, code in both the class header and code for each feature in its implementation will be counted; number of features will be based on both code features (methods) and data features (variables). The "number of features" count for C++ code will be based on code in the class header.
For Eiffel code, all code is included in the requisite class header, so class data will be taken from only the code listed there.
No effort will be made to count the LOC for individual methods; for example, a class with methods A and B might produce a total LOC count of 18 and a "number of methods" count of 2, but would not know that method A had 10 LOC and B had 8.
The C++ code for program 2A came to 233 LOC. I can reuse much of that, adding only minor features to the simple_loc_counter class to add feature count and loc/class count. Without any other data, I'll be making a very rough guess of 64 new/changed LOC.
Again, I have little data to extrapolate from. Program 2A took me quite a bit of time, about 214 minutes, so I'll guess about 1/2 that time to add the other features; I'll say about 107 minutes. I've used the % to date entries from the last planning sheet to plan time for each section here; the results can be found in the project plan summary, in the postmortem.
Program 3a will extend program 2a by declaring a new class, class_based_loc_counter, inheriting from simple_loc_counter. It will track number of features in each method by counting semicolons in the class declaration (in case feature declarations flow over line boundaries) and count LOC in methods and classes by tracking block begin/end levels. Block begin/end levels will be tracked by an integer counter, much the same as comment begin/end levels were tracked in program 2a.
I will do minor modifications to the original program 2a code. The simple_loc_counter class will have a minor refactoring, breaking the block begin/end checks into a begin check and end check; block nesting levels will be added to that class. The main program will require very minor changes, simply changing the class of the loc_counter object.
Holy smoke-- this took a great deal longer than I expected, mostly due to the vagarieties of the C++ language.
/* */ #ifndef SIMPLE_INPUT_PARSER_H #define SIMPLE_INPUT_PARSER_H #include <string> #include <iostream> //a standard "framework" for parsing a set of input lines class simple_input_parser { public: //sets input stream void set_input_stream (istream * new_input); //parse input until EOF void parse_until_eof (void); //reads single line from input, transforms it, stores it in last_line void read_line (void); //sets the last line read void set_last_line (const std::string & new_line); //last line read from input const std::string & last_line (void) const; //parses the last line read virtual void parse_last_line (void); //returns a "transformed" copy of the given line virtual std::string transformed_line (const std::string & line) const; //constructor simple_input_parser (void); //virtual destructor virtual ~ simple_input_parser (void); //resets the parser virtual void reset (void); private: //the input stream istream * m_input_stream; //the last line of input std::string m_last_line; }; #endif /* */ |
/* */ #include "simple_input_parser.h" #ifndef CONTRACT_H #include "contract.h" #endif void simple_input_parser::set_input_stream (istream * new_input) { m_input_stream = new_input; } void simple_input_parser::parse_until_eof (void) { REQUIRE (m_input_stream != NULL); while (!(m_input_stream->eof ())) { read_line (); parse_last_line (); } } void simple_input_parser::read_line (void) { REQUIRE (!(m_input_stream->eof ())); const int input_buffer_size = 255; char input_buffer[input_buffer_size]; m_input_stream->getline (input_buffer, input_buffer_size); //for some reason, the G++ standard library needs me to do this or it //doesn't register the EOF condition properly. This makes no sense //to me... char c = m_input_stream->get(); m_input_stream->putback( c ); std::string input_line (input_buffer); //no source code line should be longer than 255! CHECK (input_line.size () < 255); set_last_line (transformed_line (input_line)); } void simple_input_parser::set_last_line (const std::string & new_line) { m_last_line = new_line; } const std::string & simple_input_parser::last_line (void) const { return m_last_line; } void simple_input_parser::parse_last_line (void) { //basic version does nothing } std::string simple_input_parser::transformed_line (const std::string & line) const { return line; } simple_input_parser::simple_input_parser (void) { reset (); } simple_input_parser::~simple_input_parser (void) { } void simple_input_parser::reset (void) { m_input_stream = NULL; m_last_line = ""; } /* */ |
/* */ #ifndef SIMPLE_LOC_COUNTER_H #define SIMPLE_LOC_COUNTER_H #ifndef SIMPLE_INPUT_PARSER_H #include "simple_input_parser.h" #endif #include <string> #include <vector> //subclass of simple_input_parser that stores countable lines of code in a buffer //and can return their count. class simple_loc_counter:public simple_input_parser { public: //adds last line to the buffered lines if it is countable void parse_last_line (void); //the count of LOC int loc_count (void) const; //whether last line was comment bool last_line_is_comment (void) const; //whether last line was compiler directive bool last_line_is_compiler_directive (void) const; //whether we are in a block comment bool is_in_block_comment (void) const; //whether last line was a block begin bool last_line_is_block_begin (void) const; //whether last line was a block end bool last_line_is_block_end (void) const; //whether last line was part of a begin/end pair bool last_line_is_block_begin_or_end (void) const; //whether the last line was countable bool last_line_is_countable (void) const; //whether the last line was empty bool last_line_is_empty (void) const; //updates the block comment count void update_block_comment_count (void); //updates the block nesting level void update_block_nesting_level (void); //are we in a block? bool is_in_block (void) const; //block nesting level int block_nesting_level (void) const; //whether the last line starts with the given string bool last_line_starts_with (const std::string & search_string) const; //whether a given string starts with a search string static bool string_starts_with (const std::string & given_string, const std::string & search_string); //returns the input string stripped of leading/trailing whitespace std::string string_stripped_of_whitespace (const std::string & input_string) const; //returns the transformed line (here, the line stripped of whitespace) virtual std:: string transformed_line (const std::string & input_string) const; //constructor simple_loc_counter (void); //destructor virtual ~ simple_loc_counter (void); //resets the object virtual void reset (void); //writes the countable lines to the given output stream void write_countable_lines (ostream & ostr) const; protected: //the buffered countable lines std::vector < std::string > m_countable_lines; //the "block comment" nesting level int m_block_comment_nesting_level; //the "block" nesting level int m_block_nesting_level; //the beginning of a block comment static const std::string & block_comment_begin; //the end of a block comment static const std::string & block_comment_end; //the beginning of an inline comment static const std::string & inline_comment_begin; //the beginning of a compiler directive static const std::string & compiler_directive_begin; //the "begin block" string static const std::string & block_begin; //the "end block" string static const std::string & block_end; //whitespace characters static const std::string & whitespace_characters; }; #endif /* */ |
/* */ #include "simple_loc_counter.h" #ifndef YAK_MIN_MAX_H #include "yak_min_max.h" #endif #ifndef CONTRACT_H #include "contract.h" #endif void simple_loc_counter::parse_last_line (void) { if (last_line_is_countable ()) { m_countable_lines.push_back (last_line ()); } update_block_comment_count (); update_block_nesting_level (); } int simple_loc_counter::loc_count (void) const { return m_countable_lines.size (); } bool simple_loc_counter::last_line_is_comment (void) const { bool Result = false; if (last_line_starts_with (block_comment_begin) || last_line_starts_with (inline_comment_begin) || is_in_block_comment ()) { Result = true; } return Result; } bool simple_loc_counter::last_line_is_compiler_directive (void) const { bool Result = false; if (last_line_starts_with (compiler_directive_begin)) { Result = true; } return Result; } bool simple_loc_counter::is_in_block_comment (void) const { bool Result = false; if (m_block_comment_nesting_level > 0) { Result = true; } return Result; } bool simple_loc_counter::last_line_is_block_begin (void) const { bool Result = false; if (last_line_starts_with (block_begin)) { Result = true; } return Result; } bool simple_loc_counter::last_line_is_block_end (void) const { bool Result = false; if (last_line_starts_with (block_end)) { Result = true; } return Result; } bool simple_loc_counter::last_line_is_block_begin_or_end (void) const { return (last_line_is_block_begin () | last_line_is_block_end ()); } bool simple_loc_counter::last_line_is_empty (void) const { return (last_line ().length () == 0); } bool simple_loc_counter::last_line_is_countable (void) const { bool Result = true; if ((last_line_is_comment ()) || (last_line_is_block_begin_or_end ()) || (last_line_is_compiler_directive ()) || (last_line_is_empty ())) { Result = false; } return Result; } void simple_loc_counter::update_block_comment_count (void) { //count through the string; add 1 to the block comment count if the begin //string is encountered, subtract one if the end string is encountered. for (unsigned int i = 0; i < last_line ().length (); ++i) { std::string line_remaining = last_line ().substr (i, last_line ().length ()); if (string_starts_with (line_remaining, block_comment_begin)) { ++m_block_comment_nesting_level; } else if (string_starts_with (line_remaining, block_comment_end)) { --m_block_comment_nesting_level; } } } void simple_loc_counter::update_block_nesting_level (void) { if (last_line_is_block_begin ()) { ++m_block_nesting_level; } else if (last_line_is_block_end ()) { --m_block_nesting_level; } ENSURE (m_block_nesting_level >= 0); } bool simple_loc_counter::is_in_block (void) const { return (m_block_nesting_level > 0); } int simple_loc_counter::block_nesting_level (void) const { return m_block_nesting_level; } bool simple_loc_counter:: last_line_starts_with (const std::string & search_string) const { return string_starts_with (last_line (), search_string); } bool simple_loc_counter::string_starts_with (const std::string & given_string, const std::string & search_string) { int substring_size = yak_min (given_string.length (), search_string.length ()); std::string substring = given_string.substr (0, substring_size); bool Result = (substring == search_string); return Result; } std::string simple_loc_counter::string_stripped_of_whitespace (const std:: string & input_string) const { std::string::size_type start = input_string.find_first_not_of (whitespace_characters); if (start == input_string.npos) { start = 0; } std::string::size_type end = input_string.find_last_not_of (whitespace_characters); if (end == input_string.npos) { end = input_string.length (); } if (end < input_string.length ()) { ++end; } std::string Result = input_string.substr (start, end); return Result; } std::string simple_loc_counter::transformed_line (const std::string & input_string) const { return string_stripped_of_whitespace (input_string); } simple_loc_counter::simple_loc_counter (void) { reset (); } simple_loc_counter::~simple_loc_counter (void) { } void simple_loc_counter::reset (void) { m_countable_lines.clear (); m_block_comment_nesting_level = 0; m_block_nesting_level = 0; } void simple_loc_counter::write_countable_lines (ostream & ostr) const { for (std::vector < std::string >::const_iterator iter = m_countable_lines.begin (); iter != m_countable_lines.end (); ++iter) { ostr << *iter << "\n"; } } const std::string & simple_loc_counter::block_comment_begin = std::string( "/" ) + std::string ("*"); const std::string & simple_loc_counter::block_comment_end = std::string( "*" ) + std::string( "/" ); const std::string & simple_loc_counter::inline_comment_begin = "//"; const std::string & simple_loc_counter::compiler_directive_begin = "#"; const std::string & simple_loc_counter::block_begin = "{"; const std::string & simple_loc_counter::block_end = "}"; const std::string & simple_loc_counter::whitespace_characters = " \t\n\0x32"; /* */ |
/* */ #ifndef CLASS_METRIC_H #define CLASS_METRIC_H class class_metric { public: //LOC in the class int loc; //number of features int feature_count; class_metric (void); }; #endif /* */ |
/* */ #include "class_metric.h" class_metric::class_metric (void): loc (0), feature_count (0) { } /* */ |
/* */ #ifndef CLASS_BASED_LOC_COUNTER_H #define CLASS_BASED_LOC_COUNTER_H #ifndef SIMPLE_LOC_COUNTER_H #include "simple_loc_counter.h" #endif #ifndef CLASS_METRIC_H #include "class_metric.h" #endif #include <map> class class_based_loc_counter:public simple_loc_counter { protected: //mapping of class names to metrics std::map < std::string, class_metric > m_class_map; //are we in a class declaration? bool m_in_class_declaration; //are we in a class feature implementation? bool m_in_class_feature; //name of the class we're in std::string m_current_class_name; static const std::string class_begin; static const std::string scope_operator; std::string m_previous_line; public: //adds the last line to method/class counts as appropriate virtual void parse_last_line (void); std::string class_name_from_class_begin (const std::string & str) const; std::string class_name_from_external_feature (const std::string & str) const; bool last_line_is_class_begin (void) const; bool last_line_is_class_end (void) const; bool last_line_is_feature_begin (void) const; bool last_line_is_feature_end (void) const; bool last_line_is_data_feature (void) const; bool last_line_declares_feature (void) const; bool last_line_contains_scope_operator (void) const; bool last_line_contains_known_class_before_scope_operator (void) const; void adjust_class_feature_count (const std::string & class_name, int adjustment); void adjust_class_loc_count (const std::string & class_name, int adjustment); bool has_entry_for_class_name (const std::string & class_name) const; const std::map < std::string, class_metric > &class_map (void) const; class_based_loc_counter (void); virtual void reset (void); }; #endif /* */ |
/* */ #include "class_based_loc_counter.h" #ifndef CONTRACT_H #include "contract.h" #endif const std::string class_based_loc_counter::class_begin ("class "); const std::string class_based_loc_counter::scope_operator ("::"); class_based_loc_counter::class_based_loc_counter (void): simple_loc_counter () { reset (); } void class_based_loc_counter::reset (void) { simple_loc_counter::reset(); m_class_map.clear (); m_in_class_declaration = false; m_in_class_feature = false; m_current_class_name = "UNSET"; m_previous_line = ""; } void class_based_loc_counter::parse_last_line (void) { simple_loc_counter::parse_last_line (); //cout << m_class_map[ "class_based_loc_counter" ].loc << ":" << m_countable_lines.size() << ":" << last_line() << "\n"; if (last_line_is_countable ()) { if (last_line_is_class_begin ()) { CHECK (m_in_class_declaration == false); m_in_class_declaration = true; m_current_class_name = class_name_from_class_begin (last_line ()); } if (m_in_class_declaration) { adjust_class_loc_count (m_current_class_name, 1); if (last_line_declares_feature ()) { adjust_class_feature_count (m_current_class_name, 1); } } if (last_line_is_feature_begin ()) { //cout << "<<begin feature; previous line = " << m_previous_line << ">>"; CHECK (m_in_class_feature == false); m_in_class_feature = true; m_current_class_name = class_name_from_external_feature (last_line ()); //add one for the return type declaration, as indent usu puts it on the previous line if ((m_previous_line.length () > 0)) { //cout << "*" << m_previous_line; adjust_class_loc_count (m_current_class_name, 1); } } if (m_in_class_feature) { adjust_class_loc_count (m_current_class_name, 1); } if (last_line_is_data_feature ()) { adjust_class_loc_count (class_name_from_external_feature (last_line ()), 1); if (m_previous_line.length () > 0) { //cout << "&" << m_previous_line; adjust_class_loc_count (m_current_class_name, 1); } } } if (last_line_is_class_end ()) { CHECK (block_nesting_level () == 0); CHECK (m_in_class_declaration == true); CHECK (m_in_class_feature == false); m_in_class_declaration = false; } if (last_line_is_feature_end ()) { //cout << "<<end feature, nesting:" << block_nesting_level() << ">>"; CHECK (block_nesting_level () == 0); CHECK (m_in_class_declaration == false); CHECK (m_in_class_feature == true); m_in_class_feature = false; } m_previous_line = last_line (); } std::string class_based_loc_counter:: class_name_from_class_begin (const std::string & str) const { REQUIRE (last_line_is_class_begin ()); std::string::size_type class_begin_position = last_line ().find (class_begin); CHECK (class_begin_position != last_line ().npos); std::string::size_type next_white_space_position = last_line ().find_first_of (whitespace_characters, class_begin_position); CHECK (class_begin_position != last_line ().npos); std::string::size_type next_word_position = last_line ().find_first_not_of (whitespace_characters, next_white_space_position); CHECK (next_word_position != last_line ().npos); std::string whitespace_characters_and_colon = whitespace_characters; whitespace_characters_and_colon.append (":"); std::string::size_type end_of_class_name_position = last_line ().find_first_of (whitespace_characters_and_colon, next_word_position); if (end_of_class_name_position == last_line ().npos) { end_of_class_name_position = last_line ().length (); } std::string Result = last_line ().substr (next_word_position, end_of_class_name_position - next_word_position); //cout << Result; return Result; } std::string class_based_loc_counter:: class_name_from_external_feature (const std::string & str) const { //this is used by last_line_is_feature_begin, so we can't use it here as //a requirement... be cautious! std::string::size_type scope_operator_position = last_line ().length (); while (scope_operator_position != last_line ().npos) { scope_operator_position = last_line ().rfind (scope_operator, scope_operator_position - 1); if (scope_operator_position != last_line ().npos); { std::string beginning_of_string = last_line ().substr (0, scope_operator_position); //cout << "<<" << beginning_of_string << ">>"; std::string::size_type begin = beginning_of_string.find_last_of (whitespace_characters); if (begin == beginning_of_string.npos) { begin = 0; } else { begin = begin + 1; } std::string possible_result = beginning_of_string.substr (begin, scope_operator_position); //cout << "POSS:" << possible_result; if (has_entry_for_class_name (possible_result)) { return possible_result; } } } return ""; } bool class_based_loc_counter::last_line_is_class_begin (void) const { bool Result = false; if (last_line_starts_with (class_begin)) { Result = true; } return Result; } bool class_based_loc_counter::last_line_is_class_end (void) const { bool Result = false; if (last_line_is_block_end () && m_in_class_declaration) { Result = true; } return Result; } bool class_based_loc_counter::last_line_contains_scope_operator (void) const { return (last_line ().find (scope_operator) != last_line ().npos); } bool class_based_loc_counter::last_line_contains_known_class_before_scope_operator (void) const { bool Result = false; if (last_line_contains_scope_operator ()) { std::string class_name = class_name_from_external_feature (last_line ()); if (has_entry_for_class_name (class_name)) { Result = true; } } return Result; } bool class_based_loc_counter::last_line_is_feature_begin (void) const { bool Result = false; if (last_line_contains_known_class_before_scope_operator ()) { if (!last_line_is_data_feature ()) { Result = true; } } return Result; } bool class_based_loc_counter::last_line_is_feature_end (void) const { bool Result = false; if (last_line_is_block_end () && m_in_class_feature && (block_nesting_level () == 0)) { Result = true; } return Result; } bool class_based_loc_counter::last_line_is_data_feature (void) const { bool Result = false; if (last_line_contains_known_class_before_scope_operator ()) { if (last_line ().find (";") != last_line ().npos) { Result = true; } } return Result; } bool class_based_loc_counter::last_line_declares_feature (void) const { bool Result = false; if (m_in_class_declaration && (last_line ().find (";") != last_line ().npos)) { Result = true; } return Result; } bool class_based_loc_counter::has_entry_for_class_name (const yak_string & class_name) const { bool Result = false; if (class_map ().find (class_name) != class_map ().end ()) { Result = true; } return Result; } void class_based_loc_counter:: adjust_class_feature_count (const std::string & class_name, int adjustment) { if (!has_entry_for_class_name (class_name)) { m_class_map[class_name] = class_metric (); } m_class_map[class_name].feature_count += adjustment; } void class_based_loc_counter:: adjust_class_loc_count (const std::string & class_name, int adjustment) { if (!has_entry_for_class_name (class_name)) { m_class_map[class_name] = class_metric (); } m_class_map[class_name].loc += adjustment; } const std::map < std::string, class_metric > & class_based_loc_counter::class_map (void) const { return m_class_map; } /* */ |
/* */ #ifndef CLASS_BASED_LOC_COUNTER_H #include "class_based_loc_counter.h" #endif #ifndef YAK_EXCEPTION_H #include "yak_exception.h" #endif istream * input_stream_from_args (int arg_count, const char **arg_vector) { istream *Result = NULL; if (arg_count == 1) { Result = &cin; } else { const char *help_text = "PSP exercise 3A: Count class and total LOC.\n Usage:\n\tpsp_3a\n\n "; cout << help_text; } return Result; } int main (int arg_count, const char **arg_vector) { //get the input stream, or print the help text as appropriate istream *input_stream = input_stream_from_args (arg_count, arg_vector); if (input_stream != NULL) { class_based_loc_counter counter; try { counter.set_input_stream (input_stream); counter.parse_until_eof (); //output the counted lines //counter.write_countable_lines( cout ); //output the loc cout << "Total LOC: " << counter.loc_count () << "\n"; cout << "classname:feature count:LOC by class:\n"; for (std::map < std::string, class_metric >::const_iterator iter = counter.class_map ().begin (); iter != counter.class_map ().end (); ++iter) { cout << iter->first << ":" << iter->second.feature_count << ":" << iter->second.loc << "\n"; } } catch (exception & e) { cout << "Aborted with exception: " << e.what () << "\n"; cout << "last line parsed: \n" << counter.last_line () << "\n"; } } } /* */ |
deferred class SIMPLE_INPUT_PARSER feature {ANY} parse_until_eof is --parses all input until an EOF is reached require input_stream /= Void; do from until input_stream.end_of_input loop read_line; if not input_stream.end_of_input then parse_last_line; end; end; end -- parse_until_eof set_input(new_input_stream: INPUT_STREAM) is --sets the input stream do input_stream := new_input_stream; end -- set_input read_line is --reads a line from standard input do input_stream.read_line; last_line := transformed_line(input_stream.last_string); end -- read_line last_line: STRING; input_stream: INPUT_STREAM; parse_last_line is deferred end -- parse_last_line transformed_line(to_transform: STRING): STRING is --transforms the line according to rules defined in subclasses do Result := to_transform; end -- transformed_line end -- class SIMPLE_INPUT_PARSER |
class SIMPLE_LOC_COUNTER --counts one form of LOC in eiffel files inherit SIMPLE_INPUT_PARSER redefine parse_last_line, transformed_line end; creation {ANY} make feature {ANY} make is do !!counted_lines.make(1,0); block_nesting_level := 0; end -- make parse_last_line is --store countable lines in an array do update_block_nesting_level; -- std_output.put_integer( block_nesting_level ) -- std_output.put_string( ":" + last_line + "%N" ) if last_line_is_countable then counted_lines.add_last(last_line); end; end -- parse_last_line counted_lines: ARRAY[STRING]; --array containing countable lines block_nesting_level: INTEGER; --nesting level of syntactic blocks update_block_nesting_level is -- update the nesting level of syntactic blocks according to -- the current line do if last_line_is_block_begin then block_nesting_level := block_nesting_level + 1; end; if last_line_is_block_end then block_nesting_level := block_nesting_level - 1; end; ensure block_nesting_level >= 0; end -- update_block_nesting_level loc_count: INTEGER is --number of lines counted as LOC do Result := counted_lines.count; end -- loc_count last_line_is_comment: BOOLEAN is do if last_line_starts_with(comment_begin) then Result := true; else Result := false; end; end -- last_line_is_comment comment_begin: STRING is "--"; last_line_is_compiler_directive: BOOLEAN is false; in_block_comment: BOOLEAN is false; last_line_starts_with(test_string: STRING): BOOLEAN is do if last_line.has_prefix(test_string) then Result := true; else Result := false; end; end -- last_line_starts_with last_line_ends_with(test_string: STRING): BOOLEAN is do if last_line.has_suffix(test_string) then Result := true; else Result := false; end; end -- last_line_ends_with last_line_is_countable: BOOLEAN is do if last_line_is_comment or last_line_is_block_begin_or_end or last_line_is_empty then Result := false; else Result := true; end; end -- last_line_is_countable last_line_starts_with_any_of(strings: ARRAY[STRING]): BOOLEAN is local index: INTEGER; do Result := false; from index := strings.lower; until index > strings.upper loop if last_line_starts_with(strings.item(index)) then Result := true; end; index := index + 1; end; end -- last_line_starts_with_any_of block_beginners: ARRAY[STRING] is once Result := <<"do","if","from","class ","redefine","check","deferred","once">>; end -- block_beginners last_line_is_block_begin: BOOLEAN is do if last_line_starts_with_any_of(block_beginners) then Result := true; else Result := false; end; end -- last_line_is_block_begin last_line_is_block_end: BOOLEAN is do if last_line_starts_with("end") then Result := true; else Result := false; end; end -- last_line_is_block_end last_line_is_block_begin_or_end: BOOLEAN is do if last_line_is_block_begin or last_line_is_block_end then Result := true; else Result := false; end; end -- last_line_is_block_begin_or_end last_line_is_empty: BOOLEAN is do if last_line.empty then Result := true; else Result := false; end; end -- last_line_is_empty transformed_line(string: STRING): STRING is do Result := string_stripped_of_whitespace(string); end -- transformed_line string_stripped_of_whitespace(string: STRING): STRING is do Result := string.twin; Result.replace_all('%T',' '); Result.left_adjust; Result.right_adjust; end -- string_stripped_of_whitespace print_counted_lines(output: OUTPUT_STREAM) is local index: INTEGER; do from index := counted_lines.lower; until not counted_lines.valid_index(index) loop output.put_string(counted_lines.item(index)); output.put_string("%N"); index := index + 1; end; end -- print_counted_lines end -- class SIMPLE_LOC_COUNTER |
class CLASS_METRIC creation {ANY} make feature {ANY} loc: INTEGER; --lines of code for this class feature_count: INTEGER; --number of features in this class adjust_loc(adjustment: INTEGER) is --adjusts loc by given amount do loc := loc + adjustment; end -- adjust_loc adjust_feature_count(adjustment: INTEGER) is --adjusts feature count by given amount do feature_count := feature_count + adjustment; end -- adjust_feature_count make is do loc := 0; feature_count := 0; end -- make end -- CLASS_METRIC |
class CLASS_BASED_LOC_COUNTER --counts one form of LOC in eiffel files, giving a count of --LOC/class and number of features/class inherit SIMPLE_LOC_COUNTER redefine parse_last_line, make end; creation {ANY} make feature {ANY} class_map: DICTIONARY[CLASS_METRIC,STRING]; in_class: BOOLEAN; current_class_name: STRING; class_beginners: ARRAY[STRING] is once Result := <<"class ","deferred class ">>; end -- class_beginners last_line_is_class_begin: BOOLEAN is --whether last line starts a class declaration do if last_line_starts_with_any_of(class_beginners) then Result := true; else Result := false; end; end -- last_line_is_class_begin last_line_is_class_end: BOOLEAN is --whether last line ends class declaration do if last_line_is_block_end and block_nesting_level = 0 then Result := true; else Result := false; end; end -- last_line_is_class_end last_line_declares_feature: BOOLEAN is --does last line begin new feature? do Result := false; -- if last_line_ends_with( "is" ) and ( block_nesting_level = 1 ) then -- Result := true -- else -- Result :=false -- end if in_class and block_nesting_level = 1 and not last_line_declares_feature_clause and not in_local_clause then if last_line_is_countable and (last_line.has_string(" is") or last_line.has_string(":")) then Result := true; end; end; end -- last_line_declares_feature last_line_declares_feature_clause: BOOLEAN is do if last_line_starts_with("feature ") then Result := true; else Result := false; end; end -- last_line_declares_feature_clause feature_clause_encountered: BOOLEAN; blank_metric_entry: CLASS_METRIC is once !!Result.make; end -- blank_metric_entry ensure_class_entry(name: STRING) is --ensure that class_map has an entry for name do if not class_map.has(name) then class_map.put(blank_metric_entry.twin,name); end; end -- ensure_class_entry adjust_class_feature_count(name: STRING; adjustment: INTEGER) is --adjust method count of class metric entry do ensure_class_entry(name); class_map.at(name).adjust_feature_count(adjustment); end -- adjust_class_feature_count adjust_class_loc(name: STRING; adjustment: INTEGER) is --adjust loc of class metric entry do ensure_class_entry(name); class_map.at(name).adjust_loc(adjustment); end -- adjust_class_loc make is do Precursor; !!class_map.make; in_class := false; feature_clause_encountered := false; in_local_clause := false; end -- make adjust_in_class is --adjust whether or not we're in a class do if last_line_is_class_begin then check in_class = false; block_nesting_level = 1; end; in_class := true; current_class_name := class_name_from_last_line; end; if last_line_is_class_end then check in_class = true; block_nesting_level = 0; end; in_class := false; end; ensure in_class implies block_nesting_level > 0; end -- adjust_in_class class_name_from_last_line: STRING is --get the class declaration from last input line require last_line_is_class_begin; local split_line: ARRAY[STRING]; do split_line := last_line.split; Result := split_line.item(split_line.upper); ensure Result /= Void; end -- class_name_from_last_line adjust_feature_clause_encountered is do if in_class and last_line_declares_feature_clause then feature_clause_encountered := true; end; if not in_class then feature_clause_encountered := false; end; end -- adjust_feature_clause_encountered in_local_clause: BOOLEAN; adjust_in_local_clause is do if last_line_starts_with("local") then in_local_clause := true; end; if last_line_is_block_begin then in_local_clause := false; end; end -- adjust_in_local_clause parse_last_line is --adjust class count after precursor do Precursor; adjust_in_class; adjust_feature_clause_encountered; adjust_in_local_clause; if in_class and last_line_is_countable then adjust_class_loc(current_class_name,1); end; if in_class and last_line_declares_feature and feature_clause_encountered then adjust_class_feature_count(current_class_name,1); -- std_output.put_string( last_line + "%N" ) end; end -- parse_last_line end -- class CLASS_BASED_LOC_COUNTER |
class MAIN creation {ANY} make feature {ANY} make is local counter: CLASS_BASED_LOC_COUNTER; i: INTEGER; do !!counter.make; counter.set_input(io); counter.parse_until_eof; std_output.put_string("LOC: "); std_output.put_integer(counter.loc_count); std_output.put_string("%Nname:features:loc by class%N"); from i := counter.class_map.lower until i > counter.class_map.upper loop std_output.put_string(counter.class_map.key(i)); std_output.put_string(":"); std_output.put_integer(counter.class_map.item(i).feature_count); std_output.put_string(":"); std_output.put_integer(counter.class_map.item(i).loc); std_output.put_string("%N"); i := i + 1 end; -- std_output.put_string( "Block nesting level: " ) -- std_output.put_integer( counter.block_nesting_level ) end -- make end -- class MAIN |
A few errors in compilation with the C++ code; also noticed a few design errors here, but again most compile errors were pretty minor, having to do with improper syntax, feature names, etc. My unfamiliarity with the Eiffel language generated more errors there.
Oh my. This was a heinous 144-minute session for the C++ code, almost entirely spent on vagarieties of the C++ language (discerning a class name from a feature implemented outside the class declaration while arguments or the return type contain namespaces, dealing with class declarations to list code, etc). There are still a few very minor problems, but frankly I'm willing to keep the code, as the problems generate only about 1-2 LOC difference (applied to total LOC but not class LOC). The Eiffel program took quite a bit longer to test than I'd imagined thanks to Eiffel's asymmetric block constructs (what I'm getting at is that "end" ends a block, but many, many things can begin one, unlike C++'s symmetric "{" and "}". I also worry that my defect recording for the Eiffel program was incomplete as I became distracted by learning how to do things and let my attention slip from the PSP. The results for previous programs are as follows:
Table 3-2. Test Results -- Program 3A, C++
Program Number | [Class] Name | Number of Methods | [Class] LOC | Total Program LOC |
1A | number_list | 7 | 54 | |
82 | ||||
2A | simple_input_parser | 12 | 52 | |
simple_loc_counter | 26 | 138 | ||
212 | ||||
3A | class_based_loc_counter | 24 | 200 | |
class_metric | 3 | 7 | ||
simple_input_parser | 12 | 52 | ||
simple_loc_counter | 32 | 177 | ||
463 |
Table 3-3. Test Results -- Program 3A, Eiffel
Program Number | [Class] Name | Number of Methods | [Class] LOC | Total Program LOC |
1A | NUMBER_LIST | 7 | 52 | |
MAIN | 1 | 18 | ||
70 | ||||
2A | SIMPLE_INPUT_PARSER | 7 | 19 | |
SIMPLE_LOC_COUNTER | 15 | 52 | ||
MAIN | 1 | 11 | ||
82 | ||||
3A | CLASS_BASED_LOC_COUNTER | 20 | 75 | |
CLASS_METRIC | 5 | 12 | ||
SIMPLE_INPUT_PARSER | 7 | 19 | ||
SIMPLE_LOC_COUNTER | 22 | 84 | ||
MAIN | 1 | 24 | ||
214 |
Table 3-4. Project Plan Summary
Student: | Victor B. Putz | Date: | 000106 |
Program: | Class/Feature/LOC counter | Program# | 3A |
Instructor: | Wells | Language: | C++ |
Program Size | Plan | Actual | To date |
Base | 212 | ||
Deleted | 1 | ||
Modified | 11 | ||
Added | 252 | ||
Reused | 0 | 0 | |
Total New and Changed | 64 | 263 | 557 |
Total LOC | 463 | 730 | |
Total new/reused |
Time in Phase (min): | Plan | Actual | To Date | To Date% |
Planning | 8 | 13 | 37 | 6 |
Design | 15 | 25 | 69 | 11 |
Code | 32 | 86 | 185 | 29 |
Compile | 10 | 12 | 43 | 7 |
Test | 36 | 144 | 254 | 39 |
Postmortem | 6 | 30 | 50 | 8 |
Total | 107 | 310 | 638 | 100 |
Defects Injected | Actual | To Date | To Date % | |
Plan | 0 | 0 | 0 | |
Design | 4 | 15 | 23 | |
Code | 19 | 46 | 70 | |
Compile | 0 | 2 | 3 | |
Test | 1 | 3 | 5 | |
Total development | 24 | 67 | 100 | |
Defects Removed | Actual | To Date | To Date % | |
Planning | 0 | 0 | 0 | |
Design | 0 | 0 | 0 | |
Code | 4 | 15 | 22 | |
Compile | 8 | 26 | 39 | |
Test | 12 | 26 | 39 | |
Total development | 24 | 67 | 100 | |
After Development | 0 | 0 |
Eiffel code/compile/test |
Time in Phase (min) | Actual | To Date | To Date % |
Code | 41 | 87 | 40 |
Compile | 28 | 56 | 26 |
Test | 61 | 74 | 34 |
Total | 130 | 217 | 100 |
Defects Injected | Actual | To Date | To Date % |
Design | 1 | 4 | 10 |
Code | 18 | 38 | 90 |
Compile | 0 | 0 | 0 |
Test | 0 | 0 | 0 |
Total | 19 | 42 | 100 |
Defects Removed | Actual | To Date | To Date % |
Code | 0 | 1 | 2 |
Compile | 10 | 24 | 57 |
Test | 9 | 17 | 41 |
Total | 19 | 42 | 100 |
Table 3-5. Time Recording Log
Student: | Victor B. Putz | Date: | 000105 |
Program: | 3A |
Start | Stop | Interruption Time | Delta time | Phase | Comments |
000105 13:12:10 | 000105 13:38:06 | 12 | 13 | plan | |
000105 13:39:55 | 000105 14:05:45 | 0 | 25 | design | |
000105 14:13:06 | 000105 15:39:21 | 0 | 86 | code | |
000105 15:50:37 | 000105 16:03:28 | 0 | 12 | compile | |
000105 16:03:33 | 000105 18:29:20 | 1 | 144 | test | |
000105 19:15:59 | 000105 19:46:29 | 0 | 30 | postmortem | |
Table 3-6. Time Recording Log
Student: | Victor B. Putz | Date: | 000106 |
Program: | 3A |
Start | Stop | Interruption Time | Delta time | Phase | Comments |
000106 08:42:25 | 000106 09:24:10 | 0 | 41 | code | |
000106 09:24:45 | 000106 09:53:24 | 0 | 28 | compile | |
000106 09:53:52 | 000106 10:55:43 | 0 | 61 | test | |
Table 3-7. Defect Recording Log
Student: | Victor B. Putz | Date: | 000105 |
Program: | 3A |
Defect found | Type | Reason | Phase Injected | Phase Removed | Fix time | Comments |
000105 14:40:22 | md | om | design | code | 9 | forgot to add a feature to extract the class name from the class description |
000105 14:55:13 | md | om | design | code | 7 | forgot to add a feature to extract the class name from an external feature |
000105 15:21:33 | ic | ig | design | code | 3 | Added some refactorings to find scope operators, etc. |
000105 15:29:26 | ic | ig | design | code | 2 | Added "has_entry_for_class_name" feature |
000105 15:52:18 | ch | om | code | compile | 0 | Forgot to include required .h file for ENSURE macro |
000105 15:55:19 | sy | cm | code | compile | 0 | accidentally included "static" in member implementation for data members |
000105 15:56:20 | wn | cm | code | compile | 0 | Used "adjust_class_method_count" instead of "adjust_class_feature_count" |
000105 15:57:25 | wn | cm | code | compile | 0 | Wrong name: m_class_name instead of m_current_class_name |
000105 15:58:26 | wn | cm | code | compile | 0 | Wrong name: used "method" in name instead of "feature" |
000105 15:59:18 | wn | cm | code | compile | 0 | Missed namespace requirement, ie std::string::size_type instead of just size_type |
000105 16:00:29 | ic | om | code | compile | 0 | forgot to declare a feature as const |
000105 16:01:56 | ic | om | code | compile | 1 | forgot to add class_map accessor function implementation |
000105 16:05:06 | me | ex | code | test | 20 | Erroneous contract exception handling going on-- nothing to do with program 3a |
000105 16:26:07 | md | ig | code | test | 2 | Added more diagnostic stubs |
000105 16:31:15 | mi | om | code | test | 0 | Forgot to reset the counter during construction. Silly, that. |
000105 16:32:21 | wn | ig | code | test | 0 | Was looking for "class" at the beginning of class declarations instead of "class " |
000105 16:34:14 | wn | cm | code | test | 1 | Used manifest constant "class" instead of class constant for class begin |
000105 16:36:26 | we | kn | code | test | 13 | The logic to strip whitespace was screwy, ignoring one-character lines |
000105 16:54:59 | wa | kn | code | test | 4 | Due to improper timing of block comment nesting level calculation, end of block comments were "countable" |
000105 17:00:01 | wa | kn | code | test | 3 | Added check to make sure that the previous line had something useful on it before adding a line for external feature declaration |
000105 17:04:34 | wa | kn | test | test | 38 | Many problems handling namespaces in counting code. Unfortunately, a few errors were fixed without logging here. |
000105 17:46:36 | wa | kn | code | test | 8 | Error handling class declaration name extraction when inheritance used. |
000105 18:00:05 | ma | om | code | test | 20 | Was treating the quoted "/*" marks as nested comments when they were string data. Cheap fix: reformatted the string data. |
000105 18:22:59 | wa | om | code | test | 2 | grr... searching for the class name in a C++ external function definition mislabeled class name when preceded by &. Will modify style to make it work. |
Table 3-8. Defect Recording Log
Student: | Victor B. Putz | Date: | 000106 |
Program: | 3A |
Defect found | Type | Reason | Phase Injected | Phase Removed | Fix time | Comments |
000106 09:25:01 | md | ig | design | compile | 1 | needed to add additional functionality as Eiffel blocks can start with many beginners |
000106 09:39:13 | sy | cm | code | compile | 0 | used C-style == instead of eiffel = for comparison |
000106 09:40:25 | sy | cm | code | compile | 0 | used c-style () after no-argument eiffel feature call |
000106 09:41:09 | sy | om | code | compile | 0 | used commas instead of semicolons to delimit argument lists |
000106 09:42:14 | sy | om | code | compile | 3 | forgot to add return type to feature declaration |
000106 09:46:15 | sy | ty | code | compile | 0 | mistakenly put a space between class and feature in feature call |
000106 09:47:31 | ic | ig | code | compile | 0 | forgot to add a "last_line_ends_with" feature in simple_loc_counter |
000106 09:49:27 | wn | ig | code | compile | 1 | used "item" instead of "at" to dereference keys in dictionary |
000106 09:51:02 | wn | cm | code | compile | 0 | mistakenly declared return type of class_name_from_last_line as BOOLEAN rather than STRING |
000106 09:52:23 | sy | cm | code | compile | 0 | forgot to add creation clause |
000106 09:54:34 | ma | cm | code | test | 1 | forgot to update indexes in loops |
000106 09:56:39 | wc | cm | code | test | 3 | Was updating the class name based on end of class rather than beginning of class. Silly. |
000106 10:01:21 | wc | cm | code | test | 0 | Was checking the "in_class implies nesting_level > 0" condition at wrong place |
000106 10:02:22 | wa | cm | code | test | 1 | was printing loc:feature_count instead of feature_count:loc |
000106 10:04:46 | wc | cm | code | test | 0 | was checking for "class" and not "class " at beginning of class declaration |
000106 10:10:32 | wa | ig | code | test | 2 | was not dealing properly with "deferred class" declarations |
000106 10:13:39 | wa | ig | code | test | 20 | adjusted feature count to include non-procedure features |
000106 10:34:04 | wa | ig | code | test | 8 | changed tab characters in last_line to spacses so left_adjust would work correctly |
000106 10:45:28 | wa | ig | code | test | 7 | added check to not count variables in local clause as features |