Clean code
-
Upload
roy-nitert -
Category
Technology
-
view
576 -
download
4
description
Transcript of Clean code
Clean code
Roy Nitert
18-6-2013
GEWIS lunchlezing
© Sioux 2012 | Confidential | 2
Clean code agenda
Quality Readability Code smells Clean code characteristics
Single responsibility principle Code comments Small methods Descriptive names
Applying clean code
© Sioux 2012 | Confidential | 3
Quality
Poor SW quality cost $500 billion per year Customer expectation, free of deficiencies,
easy to use Improve quality: testing, requirements,
documentation, code reviews Studies have shown that poor readability
correlates strongly with defect density.
© Sioux 2012 | Confidential | 4
Requirements
© Sioux 2012 | Confidential | 5
Readability
To make it easier to understand and cheaper to modify (maintainability) software
The code you write will be read more times than it was written and by more people.
“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” - Martin Fowler
© Sioux 2012 | Confidential | 6
Recognizing clean code
© Sioux 2012 | Confidential | 7
Code smells
A ‘smell’ in code is a hint that something might be wrong with the code Duplicated code Long switch/if statements Long methods Poor method/variable names In-line comments Large classes
© Sioux 2012 | Confidential | 8
What is clean code ?
Characteristics of clean code: Small methods and classes Descriptive names Not too many parameters in methods (and no flags) No obvious or irrelevant comments (code must be
self describing) No redundancy (DRY: don’t repeat yourself) No magic numbers Single responsibility principle (SRP) Uniform coding style
© Sioux 2012 | Confidential | 9
SRP: Single Responsibility Principle
every object should have a single responsibility, and that responsibility should be entirelyencapsulated by the class.
a class or module should haveone, and only one, reason to change
If class names have ‘manager’ or ‘processor’ in them, they probably have multiple responsibilities
the "S" in "SOLID" stands for the single responsibility principle (“Principles Of OOD”, Robert C. Martin)
© Sioux 2012 | Confidential | 10
Comments
© Sioux 2012 | Confidential | 11
Comments
Comments do not make up for bad code Good: Legal, explanation, TODO Bad: redundant, misleading, journal, headers Don’t use a comment when you can use a
function or a variable
// Returns an instance of the Responder being tested.protected abstract Responder responderInstance();
protected abstract Responder responderBeingTested();
© Sioux 2012 | Confidential | 12
Small methods
They should be very very small Should do one thing, do it well, do it only One level of abstraction Should read like a narrative Should not have sections Blocks within if, else and while
statements should be one line long
© Sioux 2012 | Confidential | 13
/*---------------------------------------------------------------------------*/
int PO_rq_do_service( PO_object_t *object_p, DDXA_var_string_t *document_type, DDXA_var_string_t *service, DDXA_var_string_t *document_path, DDXA_var_string_t *input_file_path, DDXA_var_string_t *output_file_path, ASML_bool use_compression, PLXAtimestamp *time_stamp, POXA_var_string_array_t *parameters, DDXA_var_string_t *user_name, POXA_DocMetaDataListType *list, POXA_var_string_array_t *error_messages )/* * Description : Execute the requested service. The requested service is specified in * the 'service' parameter. The following services are defined: * put (=download), get (=upload), list, verify, and delete). * * Input(s) : document_type - type of document ( e.g. ADELfoo ) * service - Name of the service (get, put, verify, delete, list) * document_path - Service = get, delete, list: * Document identification (e.g. path/to/folder/foo.xml) * - Service = put, verify: * Not used. * input_file_path - Service = put, verify: * Name of XML formatted file (path+name) * - Service = get, delete, list * Not used. * output_file_path - Service = put, verify * Name of the XML formatted report file (path+name) * - Service = get * Name of the XML formatted file (path+name) * - Service = list * Name of the XML formatted ADELdirectory file (path+name) * (for Y* components using generic POxSERVICE interface) * - Service = delete * Not used * use_compression - Flag to indicate incoming document must unzipped and/or * outgoing document must be zipped. * time_stamp - Service = get * Time stamp of subrecipe to get (optional). * Service = put, verify, delete, list * Not used. * parameters - Array of strings pairs containing all the options and * values from the URL query string. * user_name - Name of an authenticated user * * Output(s) : list - Service = list * List of document names found, including meta data (used * for old Y* components NOT using generic POxSERVICE * interface) * - Service = put, get, verify, delete * Not used * error_messages - Array of error messages. * * Returns : OK * POXA_PARAM_ERR; * POXA_INVALID_TYPE; * POXA_ERR_RECIPE_DOES_NOT_EXIST; * POXA_SYS_ERR; * * Notes : The implementation of this function will have the following limitations: * - UserName: The UserName will be used for a user authorization mechanism. * This mechanism will not be implemented, the parameter will be ignored. * - ErrorMessages: The array of error messages will remain empty. * * Pre.Cond. : * * Post.Cond. : */{ int r = OK; int r_unzip = OK; ASML_bool single_zip = FALSE; ASML_bool is_secs = FALSE; POXA_DataType data_type = POXA_DATA_TYPE_UNDEFINED; PODS_SERVICE_ENUM service_value = PODS_SERVICE_UNKNOWN; char type_name[PODS_MAX_TYPE_LENGTH] = "";
if (( r == OK) && (object_p == NULL)) { r = PO_ERR_NULL_PARAMETER; ERXA_LOG(r, OK, ("Parameter Error, object_p = NULL")); }
if (( r == OK) && (document_type == NULL)) { r = PO_ERR_NULL_PARAMETER; ERXA_LOG(r, OK, ("Parameter Error, document_type = NULL")); }
if (( r == OK) && (service == NULL)) { r = PO_ERR_NULL_PARAMETER; ERXA_LOG(r, OK, ("Parameter Error, service = NULL")); }
if (( r == OK) && (document_path == NULL)) { r = PO_ERR_NULL_PARAMETER; ERXA_LOG(r, OK, ("Parameter Error, document_path = NULL")); }
if (( r == OK) && (input_file_path == NULL)) { r = PO_ERR_NULL_PARAMETER; ERXA_LOG(r, OK, ("Parameter Error, input_file_path = NULL")); }
if (( r == OK) && (output_file_path == NULL)) { r = PO_ERR_NULL_PARAMETER; ERXA_LOG(r, OK, ("Parameter Error, output_file_path = NULL")); }
if ((r == OK) && (time_stamp == NULL)) { r = PO_ERR_NULL_PARAMETER; ERXA_LOG(r, OK, ("Parameter Error, time_stamp = NULL")); }
if (( r == OK) && (parameters == NULL)) { r = PO_ERR_NULL_PARAMETER; ERXA_LOG(r, OK, ("Parameter Error, parameters = NULL")); }
if (( r == OK) && (user_name == NULL)) { r = PO_ERR_NULL_PARAMETER; ERXA_LOG(r, OK, ("Parameter Error, user_name = NULL")); }
if (( r == OK) && (list == NULL)) { r = PO_ERR_NULL_PARAMETER; ERXA_LOG(r, OK, ("Parameter Error, list = NULL")); }
if (( r == OK) && (error_messages == NULL)) { r = PO_ERR_NULL_PARAMETER;
Long function
if ( (r == OK) && ( strcmp( document_type->buf, "" ) == 0 ) ) { is_secs = TRUE; }
if (r == OK) { THXAtrace (CC, THXA_TRACE_INT, __func__, "> (%D, %D, %D, %D, %D, use_compression=%s, %D, %D, %D)", "document_type", "varstring", document_type, "service", "varstring", service, "document_path", "varstring", document_path, "input_file_path", "varstring", input_file_path, "output_file_path", "varstring", output_file_path, (use_compression) ? "yes" : "no", "time_stamp", "timestamp", time_stamp, "parameters", POXA_VAR_STRING_ARRAY_T_STR, parameters, "user_name", "varstring", user_name ); }
if (r == OK) { if ( strcmp(service->buf, POXA_SERVICE_GET_STRING) == 0 ) { if ( data_type == POXA_DATA_TYPE_FILE ) { service_value = PODS_SERVICE_GET_FILE_PATH; } else { service_value = PODS_SERVICE_GET; } } else if ( strcmp(service->buf, POXA_SERVICE_PUT_STRING) == 0 ) { service_value = PODS_SERVICE_PUT; } else if ( strcmp(service->buf, POXA_SERVICE_VERIFY_STRING) == 0 ) { service_value = PODS_SERVICE_VERIFY; } else if ( strcmp(service->buf, POXA_SERVICE_DELETE_STRING) == 0 ) { service_value = PODS_SERVICE_DELETE; } else if ( strcmp(service->buf, POXA_SERVICE_LIST_STRING) == 0 ) { service_value = PODS_SERVICE_LIST; } else { service_value = PODS_SERVICE_UNKNOWN; } }
if (r == OK) { if ( ( service_value == PODS_SERVICE_PUT) || ( service_value == PODS_SERVICE_VERIFY) ) { /* If compressed file sent source_xml_filename contains the filename of * xml document and can be anything. Type of document must be retreived * from the contents of the document itself. * In case a multi zip file is in the input_file, this call * will result in NOK, therefore result is directed to different result * variable (r_unzip) */ if ( is_secs ) { if ( use_compression ) { r_unzip = POGN_unzip_file( input_file_path->buf ); if (r_unzip == OK) { single_zip = TRUE; } }
/* Get document type from the file. */ r = PODS_get_type_from_doc_file( input_file_path->buf, type_name );
if (r == OK) { r = DDXA_var_string_alloc( document_type, type_name ); }
/* In case of unzip the file will need to be zipped */ if ( single_zip ) { r_unzip = POGN_zip_file( input_file_path->buf ); } } } else if ( (service_value == PODS_SERVICE_LIST) || (service_value == PODS_SERVICE_GET) || (service_value == PODS_SERVICE_GET_FILE_PATH) || (service_value == PODS_SERVICE_DELETE) ) { if ( is_secs ) { /* 'document_type' is empty string -> Document is sent via SX. */ r = PODS_extract_type_from_doc_id( document_path->buf, type_name );
if (r == OK) { r = DDXA_var_string_cat( document_type, type_name ); } else { ERXA_LOG(PO_ERR_SYS_ERROR, r, ("Error: Cannot extract document type and path from '%s'", document_path->buf)); r = PO_ERR_SYS_ERROR; } } } else { ERXA_DEACT( OK, PO_ERR_SYS_ERROR, ( "Unknown service requested." ) ); } }
if ( r == OK ) { if ( (service_value == PODS_SERVICE_GET) || (service_value == PODS_SERVICE_GET_FILE_PATH) ) { r = po_rq_handle_service_get( PO_RQ_ACTION_UPLOAD, document_type, document_path, input_file_path, output_file_path, time_stamp, parameters); } else if ( service_value == PODS_SERVICE_PUT ) { r = po_rq_handle_service_put_verify( PO_RQ_ACTION_DOWNLOAD, document_type, input_file_path,
/*---------------------------------------------------------------------------*/PO_RQ_ACTION_DOWNLOAD, document_type, input_file_path, output_file_path, use_compression, parameters, TRUE, single_zip); } else if ( service_value == PODS_SERVICE_VERIFY ) { r = po_rq_handle_service_put_verify( PO_RQ_ACTION_VERIFY, document_type, input_file_path, output_file_path, use_compression, parameters, FALSE, single_zip); } else if ( service_value == PODS_SERVICE_DELETE ) { r = po_rq_handle_service_delete( document_type, document_path, input_file_path, parameters); } else if ( service_value == PODS_SERVICE_LIST ) { r = po_rq_handle_service_list( document_type, document_path, parameters, output_file_path, list); } else { r = PO_ERR_SYS_ERROR; ERXA_LOG(r, OK, ("Error: unsupported service '%s'", service->buf)); } }
/* Zip the target file if necessary (put, verify and get). */ if ( ( r == OK ) && use_compression && ( ( service_value == PODS_SERVICE_GET ) || ( service_value == PODS_SERVICE_GET_FILE_PATH ) || ( service_value == PODS_SERVICE_PUT ) || ( service_value == PODS_SERVICE_VERIFY ) ) ) { if (single_zip) { r = POGN_zip_file(output_file_path->buf); } }
/* create POXA errorcode from internal error codes */ if (r != OK) { int l = r; int r_pr = OK; char message[ADELserviceResponse_MAX_MESSAGE_TYPE_SIZE]= "";
/* create POXA errorcode from internal error codes */ switch (l) { case PO_ERR_NULL_PARAMETER: r = POXA_PARAM_ERR;
r_pr = PLXAstr_strlcpy(message, "NULL parameter passed.", ADELserviceResponse_MAX_MESSAGE_TYPE_SIZE); break;
case PO_ERR_INVALID_TYPE: r = POXA_INVALID_TYPE;
r_pr = PLXAstr_snprintf(message, ADELserviceResponse_MAX_MESSAGE_TYPE_SIZE, "Invalid type '%s' provided.", document_type->buf); break;
case PO_ERR_NO_INSTANCE_ID_FOUND: case PO_ERR_OPENING_FILE: r = POXA_ERR_RECIPE_DOES_NOT_EXIST;
r_pr = PLXAstr_snprintf(message, ADELserviceResponse_MAX_MESSAGE_TYPE_SIZE, "File '%s' does not exist.", input_file_path->buf); break;
case PO_ERR_GETTING_LIST: case PO_ERR_GETTING_DATA: case PO_ERR_STORING_DATA: case PO_ERR_PUTTING_DATA: case PO_ERR_VERIFYING_DATA: case PO_ERR_DELETING_DATA: r = POXA_SERVICE_FAILED;
r_pr = PLXAstr_snprintf(message, ADELserviceResponse_MAX_MESSAGE_TYPE_SIZE, "Service '%s' failed for document type '%s'.", service->buf, document_type->buf); break;
case PO_ERR_SYS_ERROR: default: r = POXA_SYS_ERR;
r_pr = PLXAstr_strlcpy(message, "Internal error occurred.", ADELserviceResponse_MAX_MESSAGE_TYPE_SIZE); break; }
if (r_pr == OK) { if ( ( !is_secs ) && ( strcmp( output_file_path->buf, "" ) != 0) ) { int r_csr = OK;
r_csr = po_rq_create_service_response(output_file_path->buf, ADELserviceResponse_Error, r, message); if (r_csr != OK) { ERXA_DEACT( POXA_SYS_ERR, r_csr, ("") ); } } } else { ERXA_DEACT( POXA_SYS_ERR, r_pr, ("") ); }
ERXA_LOG(r, l,
PO_RQ_ACTION_DOWNLOAD, ("Error: Execution of service '%s' with document_type '%s' failed", service->buf, document_type->buf) ); }
THXAtrace (CC, THXA_TRACE_INT, __func__, "< (%D, %D) = %R", "list", POXA_DOCMETADATALISTTYPE_STR, list, "error_messages", POXA_VAR_STRING_ARRAY_T_STR, error_messages, r);
return r;}
© Sioux 2012 | Confidential | 14
Descriptive names
Names make software readable Helps clarify the design in your mind Use long names for long scopes Avoid encodings
© Sioux 2012 | Confidential | 15
Descriptive names example
Public int x() {
int q = 0;
int z = 0;
for (int kk = 0; kk < 10; kk++) {
if (l[z] == 10) {
q += 10 + (l[z + 1] + l[z + 2]);
z += 1;
} else if (l[z] + l[z + 1] == 10) {
q += 10 + l[z + 2];
z += 2;
} else {
q += l[z] + l[z + 1];
z += 2;
}
}
return q;
}
© Sioux 2012 | Confidential | 16
Descriptive names example
Public int score() {
int score = 0;
int frame = 0;
for (int frameNumber = 0; frameNumber < 10; frameNumber++) {
if (isStrike(frame)) {
score += 10 + nextTwoBallsForStrike(frame);
frame += 1;
} else if (isSpare(frame)) {
score += 10 + nextBallForSpare(frame);
frame += 2;
} else {
score += TwoBallsInFrame(frame);
frame += 2;
}
}
return score;
}
© Sioux 2012 | Confidential | 17
Parameters / arguments
Small number: Zero is best, followed by one, two or three
More than three is questionable Output arguments are counterintuitive Boolean arguments (flags) loudly declare than
the function does more than one thing Simpler is easy to understand and easy to test Reduce number of arguments by creating
objects out of them
© Sioux 2012 | Confidential | 18
Parameters example
Circle makeCircle(double x, double y, double radius);
public void AlignTaskCanBeStopped(bool useRealXps);
AlignTaskCanBeStopped(true);
© Sioux 2012 | Confidential | 19
Parameters example
Circle makeCircle(Point center, double radius);
public void AlignTaskCanBeStoppedWith RealXps();
public void AlignTaskCanBeStoppedWith SimulatedXps();
© Sioux 2012 | Confidential | 20
Don’t repeat yourself
Duplication DRY principle (Dave Thomas) Once, and only once (Kent Beck) Duplication is a missed opportunity for abstraction Examples:o Identical code (copy/paste)o Switch/case (or if/else) chainso Similar algorithms
Design patterns and OO itself are strategies for eliminating duplication
© Sioux 2012 | Confidential | 21
DRY example
public void scaleToOneDimension( float desiredDimension, float imageDimension) {
if (Math.abs(desiredDimension – imageDimension) < errorThreshold)
return;
float scalingFactor = desiredDimension / imageDimension;
scalingFactor = (float)(Math.floor(scalingFactor * 100) * 0.01f);
RenderOp newImage = ImageUtilities.getScaledImage( image, scalingFactor, scalingFactor);
image.dispose();
System.gc();
image = newImage;
}
public synchronized void rotate(int degrees) {
RenderOp newImage = ImageUtilities.getRotatedImage( image, degrees);
image.dispose();
System.gc();
image = newImage;
}
© Sioux 2012 | Confidential | 22
DRY example
public void scaleToOneDimension( float desiredDimension, float imageDimension) {
if (Math.abs(desiredDimension – imageDimension) < errorThreshold)
return;
float scalingFactor = desiredDimension / imageDimension;
scalingFactor = (float)(Math.floor(scalingFactor * 100) * 0.01f);
replaceImage(ImageUtilities.getScaledImage( image, scalingFactor, scalingFactor));
}
public synchronized void rotate(int degrees) {
replaceImage(ImageUtilities.getRotatedImage( image, degrees));
}
private void replaceImage(RenderedOp newImage) {
image.dispose();
System.gc();
image = newImage;
}
© Sioux 2012 | Confidential | 23
Magic numbers
Every number is a magic number
Replace it with a named constant
double circumference = radius * Math.PI * 2;
if (password.length() > 7)
public static final int MAX_PASSWORD_SIZE = 7;if (password.length() > MAX_PASSWORD_SIZE)
© Sioux 2012 | Confidential | 24
Applying clean code
Know how to write clean code Think about these small improvements
during:o New developmento Code reviewso Changing codeo Fixing bugs
So code will be easier to understand and cheaper to modify
© Sioux 2012 | Confidential | 25
Conclusion
Always code as if the guy who ends up maintaining your code will be a
violent psychopath who knows where you live.Code for readability.
© Sioux 2012 | Confidential | 26
Questions?