1)Never start coding unless you understand the task! 2)Gather requirements first. This means identify the problem and ask questions about it. Now you kind of know what to do. 3)Analyze requirements to make sure that they are consistent and complete. Now you know that whatever you are going to do is doable. 4)Produce technical specification (i.e. express the requirements in technical terms). Now you know exactly what to do. 5)Think of what steps you need to take solve the problem and express them verbally to obtain a high- level process description. Now you have a process that you can code! 6)Start coding. Problem Solving
* Split complex task in a series of simple steps. * Identify entities the problem deals with. These will be your classes. * Identify what data characterizes your entities. These will be your class properties. * Identify what actions your entities can perform. These will be your class methods. * Find repeated actions and generalize them. These will be your utility functions or utility classes. You will reuse them. * Don’t make any assumptions: check user input and make provisions for worst cases. Process Analysis
Problem: Determine if the address is valid. Requirement analysis: What rules can we define to check address against? What is address comprised of? Local domain subdomain1.subdomain2.tld Validation Task
Core Rules Single character Non-empty Local Part Non-empty domain Non-empty subdomain(s) Non-empty TLD Local Part must not end with ‘.’ Additional Rules 7) 1 < TLD length < 7 8) Local Part length < 65 9) Domain Length < ) Subdomain(s) must not begin with ‘-’ or end with ‘-’ 12) Check for invalid characters: ‘\\’, ‘”’, ‘ ’, ‘)’, ‘(‘, ‘]’, ’[‘, ’;’, ‘,’, ‘:’, ‘|’ 13) Domain cannot contain two ‘-’ in a row 14) Check TLD against the list of ICANN domains Validation Rules
General There has to be LocalPart and Domain separated by Must not contain general illegal characters (‘\\’, ‘”’, ‘ ’, ‘)’, ‘(‘, ‘]’, ’[‘, ’;’, ‘,’, ‘:’, ‘|’) LocalPart 0 < Length < 65 Must not end with ‘.’ Domain 0 < Length < 256 There has to be at least one subdomain and a TLD separated by ‘.’ Must not contain All subdomains must be valid TLD must be valid Subdomain 0 < Length < 64 Must not begin or end with ‘-’ TLD 0 < Length < 7 Must not contain ‘-’ OR limit TLD choices to domains from ICANN list. Optimized Rules / Technical Spec
Design Choice: Buffer Allocation Since you do not know sizes of strings you deal with… a)Do you make an assumption of max string length and allocate fixed-size string buffers on stack? +simpler +faster +robust -limited b)Or do you allocate memory on heap (e.g. using the new operator)? -harder -slower -err.prone +universal
Choice A: Fixed Buffers // Our Assumptions #define MAX_LOCAL_PART65 #define MAX_DOMAIN256 #define MAX_SUBDOMAIN64 #define MAX_TLD7 #define MAX_SUBDOMAINS10 // Buffers char LocalPart[MAX_LOCAL_PART]; char Domain[MAX_DOMAIN]; char TLD[MAX_TLD]; char Subdomain[MAX_SUBDOMAINS][MAX_SUBDOMAIN];
Top-Level Class: – this is the one we validate -Contains Address string (property) -Contains LocalPart (property) -Contains Domain (property) -Can be parsed into LocalPart and Domain pieces (method) -Can be validated (method)
Class class { public: (char* address); public: String Address; LocalPart LocalPart; Domain Domain; public: bool Parse(); bool IsValid(); };
Other Classes LocalPart -Contains UserName string (property) -Can be validated (method) Domain -Contains Name string (property) -Contains an array of Subdomains (property) -Contains TLD (property) -Can be parsed (method) -Can be validated (method) Subdomain -Contains Name string (property) -Can be validated (method) TLD (closely similar to Subdomain but somewhat different) -Contains Name string (property) -Can be validated (method)
LocalPart Class class LocalPart { public: LocalPart(); public: String UserName; public: bool IsValid(); private: char UserNameBuffer[MAX_LOCAL_PART]; };
Domain Class class Domain { public: Domain() { SubdomainCount = 0; } public: String Name; Subdomain Subdomain[MAX_SUBDOMAINS]; int SubdomainCount; Tld Tld; public: bool Parse(); bool IsValid(); private: char DomainBuffer[MAX_DOMAIN]; };
Subdomain Class class Subdomain { public: Subdomain(); public: String Name; public: virtual bool IsValid(); // Virtual means that the method // can be replaced in a derived // class private: char NameBuffer[MAX_DOMAIN]; };
Tld Class // Subdomain and Tld are identical, except for // slight differences in validation rules, so to avoid repetition derive Tld class from Subdomain base class class Tld: public Subdomain // Tld class is derived from // Subdomain class { public: Tld(); /* public: String Name; */ // Name is inherited from Subdomain // therefore no need to declare again public: virtual bool IsValid(); // Inherited, but we are going to // replace it since TLD validation // is different };
Checker Solution: Process 1)Separate into LocalPart and Domain 2)Separate Domain into Subdomain[] array and TLD 3)Apply part validation rules to see if any of them are violated.
Separate Local Part & Domain 1)Determine Length 2)Determine AtPosition: the position of character in 3)Copy characters 0…AtPosition to LocalPart 4)Copy characters AtPosition+1…Length to Domain
Pretend that you have already solved concrete tasks (such as copying characters, finding character index, etc.) with the help of String utility class and proceed to construct your abstract solution by referring to String and other concrete classes that you have not yet implemented. Proceed from Abstract to Concrete
(!) Find repeated tasks that can be generalized and streamlined, e.g. a)Determining string length (i.e. return the ‘length’ of ‘this string’) b)Copying a range of characters from one string into another (i.e. copy ‘length’ characters from ‘this string’ starting at ‘startPosition’ into ‘destination’ c)Finding character index (find ‘aChar’ in ‘this string’ starting at ‘startPosition’) The above (a,b,c) deal with character strings. So let’s pretend that we have a String class that can do all of the above.Implementation
// String utility class class String { public: String() // Default constructor (builds uninitialized String) { Buffer = NULL; } String(char* buffer) // String encapsulates char-array pointer { Buffer = buffer; } public: int GetLength(); int IndexOf(char aChar, int startPosition); // !! Returns -1 when aChar is not found void CopyTo(int startPosition, int length, String& destination); bool IsValid() // Sanity check { return Buffer != NULL; } private: char* Buffer; }; String Class
String Class Encapsulates char* String class encapsulates char* pointer. Therefore a String must be initialized with a valid char* pointer! char nameBuffer[100]; String name(nameBuffer);
We always start with top-level class. So let’s pretend that class is done and the validation works and we can use it, e.g. if ( .IsValid() ) cout << “ is valid”; else cout << “ is not valid”; Where Do We Start?
We start filling in blanks from the constructor of the top-level class. In the constructor we can refer to other classes we invented even though they are not yet implemented. Where Do We Go Next?
Just store the pointer to address in internal property: : (char* address) { // Here we initialize ’s internal // Address property of type String Address = String(address); } Constructor : ()
Now, since we already know what amounts to valid we can express :IsValid() concisely as follows: bool :IsValid() { // Separate into LocalPart and Domain return Parse() &&// Was parsing OK? LocalPart.IsValid() &&// LocalPart OK? Domain.IsValid();// Domain OK? } bool :IsValid()
And we know how to parse Address into LocalPart and Domain: bool :Parse() { // Find character int atPosition = 0); if ( atPosition == -1 ) return false; // Shute, not found! Therefore cannot parse. // Determine length int length = Address.GetLength(); // Copy parts in appropriate buffers Address.CopyTo(0, atPosition, LocalPart.UserName); Address.CopyTo(atPosition + 1, Length - atPosition - 1, Domain.Name); return true; } Void :Parse()
And we know what rules we need to check to make sure that LocalPart is valid: bool LocalPart::IsValid() { // We do not assume anything, so check UserName string if ( !UserName.IsValid() ) return false; int length = UserName.GetLength(); // Rule Check: length between 1 and 64 if ( length 64 ) return false; else return true; } bool LocalPart::IsValid()
And we know what rules we need to check to make sure that Domain is valid: bool Domain::IsValid() { // Extract subdomains and TLD if ( !Parse() ) return false; // Check subdomains for ( int i = 0; i < SubdomainsCount; i++ ) if ( !Subdomain[i].IsValid() ) return false; // Check TLD if ( TLD.IsValid() ) return true; else return false; } bool Domain::IsValid()
And we know what rules we need to check to make sure that Domain is valid: bool Domain::Parse() { // We do not assume anything, so check UserName string if ( !Name.IsValid() ) return false; // Contains at least one ‘.’ if ( Name.IndexOf(‘.’, 0) == -1 ) return false; // Reset SubdomainCount SubdomainCount = 0; // Extract subdomains int startPosition = 0; do { int nextPosition = Name.IndexOf(‘.’, StartPosition); if ( nextPosition != -1 ) Name.CopyTo(startPosition + 1, nextPosition - startPosition, Subdomain[SubdomainCount++]); else Name.CopyTo(startPosition + 1, nextPosition - startPosition, Tld) startPosition = nextPosition + 1; } while ( startPosition != 0 ); return true; } bool Domain::Parse()
TLD is a domain, since all domain rule checking applies with an addition of one more TLD-specific rule: TLD cannot contain a ‘-’ // Constructor does nothing special, so we simply call // the base class constructor Tld:: Tld() : Subdomain() { } bool Tld::IsValid() { return Subdomain::IsValid() && Name.IndexOf(‘-’, 0) == -1; } bool Tld::IsValid()
Just keep filling in the blanks and your program will be done! In the process you will likely modify your class definitions to accommodate your programming needs as you discover new things. Class design / programming is an iterative approach. Thus changing and going back and forth is inevitable and normal. And So On and So Forth