К нам приезжал Chief Software Architect со стороны заказчика. Надо сказать весьма приятный и умный дядька. Так вот этот дядька сегодня, по нашей просьбе, сделал для нас презентацию, рассказывающую основные приципы разработки хорошего кода. Основана она на книге Code Complete By Steve McConnell.
Внизу содержание лекции.
- This Class is not a Lecture, Speak Up!
- Objective of this Course: To teach good software construction principles with emphasis on Unit testing.
- API Development
These practices will become critical as we grow and release APIs to the public. Our code will say a lot about the quality of the product.
- Increase Development Efficiency
- Reduce Time Spent at Work
B) What is Software Construction?
- Construction is the process of designing, developing, debugging, and testing software (Cyclical).
- Software Engineer is responsible for all phases.
- QA, ideally, should not find bugs. The QA team is there to make sure we did our jobs. That means TEST!
C) Why is Software Construction Important?
- Construction is Large Part of Project
- By improving construction, the individual programmer can improve productivity enormously.
Sackman, Erikson, and Grant: 1968 Study shows individual programmer productivity varies by factor of 10 to 20. Wide gap between the average and the best suggests there is great potential for average programmer to improve.
- The source code is often the only accurate description of the software.
Requirements specifications and design documents can become out of date, but the source code is always up to date. Consistent application of techniques for source code improvement make the difference between a hard to read and maintain nightmare and a detailed, therefore informative program.
- Construction is the only activity that is guaranteed to be done.
Ideal software project goes through careful requirements analysis and architectural design before construction begins. It also undergoes comprehensive, statistically controlled system testing after construction. Imperfect, real-world projects, however, often skip analysis, and design and jump directly into construction. Testing is dropped because there are too many errors to fix and time has run out. But no matter how rushed or poorly planned a project is, you can’t drop construction; it’s where the rubber meets the road. Improving construction improves any software development effort, now matter how abbreviated.
D) “Code Complete” – Introduced to me by my mentor + “Rapid Development”.
II. Code Development: The Routine
A) Characteristics of High-Quality Routines
What is a Routine?
- “A routine is an individual function or procedure invocable for a single purpose.”
- “Aside from the computer itself, the routine is the single greatest invention in computer science.”
Valid Reasons to Create a Routine
- Reducing Complexity – Number one reason. Write a routine to hide information so you won’t have to think about it.
- Avoiding Duplicate Code
- Limiting Effects of Changes – Isolate areas that are likely to change so that the effects of the changes are limited to the scope of the routine.
- Hiding Sequences – If several events happen in the same order every time, hid that from the rest of the program. That way, if the order changes, the code is easy to update.
- Improving Performance – Code only needs to be optimized in one place with a routine.
- Making Central Points of Control – Ex: Reading and modifying the contents of internal data structures. If the structures change, only the access routines need to change.
- Hiding Data Structures
- Hiding Global Data
- Promoting Code Reuse
- Planning for a Family of Programs – Put areas likely to change into routines. That way, you can modify or add new routines to extend the functionality of the system (e.g. Adapter Pattern).
- Making Sections of Code More Readable – If you have the following code:
if(node != null) while(node.next != null) node = node.next; leafName = node.name; else leafName = “”; you can read a statement like: leafName = GetLeafName(node);
- Isolating Complex Operations
- Isolating Use of Non-Standard Language Functions – Java, like most languages include non-standard language features. Handy but dangerous. Keep separate if changes likely.
- Simplifying Complicated Boolean Tests
- Operations that seem too simple to put into routines
- One of the strongest mental blocks to creating routines is a reluctance to create a simple routine for a simple purpose. Constructing a routine that contains 3 lines of code may seem like overkill but often simple processes become complex ones.
Good Routine Names
- Differences between procedures and functions
- Procedure – Operates on data but does not return a value.
- Function – Operates on data and returns a value.
- For a procedure, use a strong verb followed by an object.
- A procedure with functional cohesion usually performs an operation on an object. The procedure should name should indicate what it does and what it does it on.
- Good procedure names: printReport(), calcMonthlyRevenue(), and checkOrderInfo()
- Note: In object-oriented languages, don’t need to include the name of the object itself because the object is included in the call. Ex: Report.print(), MonthlyRevenues.calc(), OrderInfo.check().
- For a function name, use a description of the return value.
- Ex: cos(), nextCustomerId(), mediaReady(), and currentTime()
- Avoid meaningless or “wishy-washy” verbs.
- Some verbs are elastic, stretched to cover just about any meaning. Routine names like handleCalculation(), performServices(), processInput(), and dealWithOutput() don’t tell you what the routines do. At best you can guess they do something with calculations, services, input, and output. Replacing handleOutput() with formatAndPrintOutput() gives a clearer explination of what the routine does.
- Warning: Often a routine name is difficult to come up with because the routine itself is poorly designed. If the routine suffers from weakness of purpose, then the weak name may be a symptom.
- Describe everything the routine does
- In the routine’s name, describe all the outputs and side effects. If a routine computes report totals and updates a customer database, then the name computeReportTotals() is not an adequate name. computReportTotalsAndUpdateCustomerRecord() is adequate, but is too long and silly. If you have routines that do numerous things, you’ll have long, silly names. The cure is not to use less-descriptive names; the cure is to ensure routines do exactly one thing and do it well.
- Establish conventions for common operations
- The OS/2 Presentation Manager, for example, used a “get” prefix for destructive input and “query” for non-destructive input. Thus, getInputChar() returned the current input character and cleared the input buffer. queryInputChar() also returned the current input character, but left the buffer intact.
- Cohesion refers to how closely the operations in a routine are related. A function like sin() is perfectly cohesive because its entire existence is devoted to doing one thing. A function like sinAndTan() has lower cohesion because it tries to do more than one thing.
- One study of 450 routines found that 50% of the highly cohesive routines were fault free, whereas only 18% of those with low cohesion were fault free (Card, Church, and Agresti 1986).
- Another study of 450 routines (not the same 450 as above) found that routines with the highest coupling-to-strength ratios had 7 times as many errors and were 20 times as costly to fix (Shelby and Basili 1991).
- Acceptable Cohesion
- Functional Cohesion – This is the strongest and best cohesion occurring when a routine does exactly one operation. Examples include sin(), getCustomerName(), and getIconLocation(). Case example: a routine that calculates a customer’s age given a birth date.
- Sequential Cohesion – Occurs when a routine contains operations that must occur in a specific order and share data from one step to the next. Case example: a routine that calculates a customer’s age and whether they are over the minimum age to use the system.
- Communicational Cohesion – Occurs when operations in a routine share the same data but aren’t related in any other way. For example, getNameAndChangePhoneNumber() would have communicational cohesion if both the name and phone number were in the same record and they don’t need to be dealt with in any particular order. On a practical level, both actions would probably be best if split into two individual routines and called separately. The original routine has communicational cohesion so it is still acceptable but far from the ideal of doing one thing and doing it well. Case example: A routine that prints a summary report when its done, and reinitializes the data passed in to it. This is communicational cohesion because the operations are related only by the fact that they share the same data.
- Temporal Cohesion – Occurs when operations are combined into an routine because they are done at the same time. Typical examples include startup(), completeNewCustomerRegistration(), and shutdown(). However, some would argue that temporal cohesion is bad because its sometimes associated with bad programming practices such as having a hodgepodge of code in a single startup() routine. The solution is to think of temporal routines as organizers of of other events. The temporal routine should directly call other routines to perform the operation rather than doing the dirty work itself. Case example: purchaseTitles(). It does all the steps necessary to complete a transaction by grouping together other routines in the necessary order. It is an organizer of the events and nothing more.
- Unacceptable Cohesion
- Procedural Cohesion – Occurs when operations are done in a specific order but do no share data. For example, user’s might like reports printed in a certain order (ex: printing a revenue report, expense report, a list of employee phone numbers, and invitations to a company picnic. This routine is difficult to name and that is a tip that it is poorly designed.
- Logical Cohesion – Occurs when several operations are stuffed into a routine and one of the operations is selected by a control flag that is passed in. An example is inputAll() which inputs customer names, employee time card information, or inventory data depending on a flag passed into it.
- Degree of coupling refers to the strength of the connection between two routines.
- Try to make routines that depend little on other routines.
- Examples of Loose Coupling
- sin() is a good example because everything it needs to know is passed into it with one value representing an angle in degrees.
- Examples of Tight Coupling
- initVars(var1, var2, var3, …, varN) is more tightly coupled because the calling routine virtually knows everything that is going on inside it.
- Functions that take SOAP as parameter.
- Keeps function married to SOAP interface.
- If later needs to become a utility class, must construct a SOAP message to call it!
-Not clear from looking at routine what values (parameters) are needed
- You never know how others will call your routines in the future so you need to protect yourself from bad data.
- All public methods should validate their input data first thing. Private methods can make more assumptions.
- Assertions are one of the best defenses. Use assertions to document assumptions made in the code. For example, if you have division in a routine, a good assertions would first verify the denominator is not zero, even if the case should never happen. Assertions are free checks that not only protect your code and find errors later on, but they are an excellent documentation tool that makes your assumptions clear.
- Since assertions may be disabled, never put critical logic in them.
How to Use Routine Parameters
- Put parameters in input-modify-output order
- Make all input parameters final
- If several routines use similar parameters, put the parameters in a consistent order.
- Put status or error parameters last.
- Don’t use routine parameters as working variables.
- Limit the number of parameters to about seven.
- Psychological research has found that people cannot generally keep track of more than seven pieces of information at once (Miller 1956).
- Consider a complex data type if more than seven are needed.
- If you consistently need more than seven, then your routines may be too tightly coupled. Consider whether a flaw in the design exists.
- Pass only the parts of a structured variable that the routine needs, unless the routine uses almost all of them.
When is the Routine Complete?
- Voltare: “A book is complete when there is nothing more to add and nothing more to take away.” Same is true for software construction.
B) Themes in Software Craftsmanship
- Conquer Complexity
- Write Programs for People First, Computers Second
C) Intelligence and Humility
- Intellectual Honesty
- Communication and Cooperation
- Creativity and Discipline
- Characteristics That Don’t Matter As Much As You Might Think
D) Steps in Building a Routine
- Summary of Steps in Building a Routines
- PDL for Pros
- Design the Routine
- Code The Routine
- Check the Code Formally
- Iterate, Repeatedly, Again and Again