Codigo Limpio para Programacion
Codigo Limpio para Programacion
Codigo Limpio para Programacion
Code is clean if it can be understood easily by everyone on the team. With understandability comes readability, changeability, extensibility and maintainability. All the things needed to keep a project going over a long time without accumulating up a large amount of technical debt. optimal Responsiveness
Smells Rigidity
The software is difficult to change. A small change causes a cascade of subsequent changes.
Fragility
The software breaks in many places due to a single change.
Fields holding data that does not belong to the state of the instance but are used to hold temporary data. Use local variables or extract to a class abstracting the performed action.
Over Configurability
high
high
Immobility
You cannot reuse parts of the code in other projects because of involved risks and high effort.
Prevent configuration just for the sake of it or because nobody can decide how it should be. Otherwise, this will result in overly complex, unstable systems.
Responsiveness to change
Micro Layers
Do not add functionality on top, but simplify overall.
actual
Responsiveness
CoC
Viscosity of Design
Taking a shortcut and introducing technical debt requires less effort than doing it right.
Always look for the root cause of a problem. Otherwise, it will get you again and again.
Building, testing and other tasks take a long time. Therefore, these activities are not executed properly by everyone and technical debt is introduced.
If one module depends upon another, that dependency should be physical, not just logical. Dont make assumptions.
+ + + +
Technical Debt
low
low
Needless Repetition
Writing clean code from the start in a project is an investment in keeping the cost of change as constant as possible throughout the lifecycle of a software product. Therefore, the initial cost of change is a bit higher when writing clean code (grey line) than quick and dirty programming (black line), but is paid back quite soon. Especially if you keep in mind that most of the cost has to be paid during maintenance of the software. Unclean code results in technical debt that increases over time if not refactored into clean code. There are other reasons leading to Technical Debt such as bad processes and lack of documentation, but unclean code is a major driver. As a result, your ability to respond to changes is reduced (red line).
Code contains lots of code duplication: exact code duplications or design duplicates (doing the same thing in a different way). Making a change to a duplicated piece of code is more expensive and more error-prone because the change has to be made in several places with the risk that one place is not changed accordingly.
Continuous Integration
Assure integrity with Continuous Integration
Feature Envy
Do not override warnings, errors, exception handling they will catch you.
Opacity
The code is hard to understand. Therefore, any change takes additional time to first reengineer the code and is more likely to result in defects due to not understanding the side effects.
The methods of a class should be interested in the variables and functions of the class they belong to, and not the variables and functions of other classes. When a method uses accessors and mutators of some other object to manipulate the data within that object, then it envies the scope of the class of that other object. It wishes that it were inside that other class so that it could have direct access to the variables it is manipulating.
Things that dont depend upon each other should not be artificially coupled. If, for example, the order of some method calls is important, then make sure that they cannot be called in the wrong order.
+ + + + + +
Most software defects are introduced when changing existing code. The reason behind this is that the developer changing the code cannot fully grasp the effects of the changes made. Clean code minimises the risk of introducing defects by making the code as easy to understand as possible.
If you have a constant such as default or configuration value that is known and expected at a high level of abstraction, do not bury it in a low-level function. Expose it as an argument to the low-level function called from the high-level function.
Transitive Navigation
Aka Law of Demeter, writing shy code. A module should know only its direct dependencies.
Dont Be Arbitrary
Have a reason for the way you structure your code, and make sure that reason is communicated by the structure of the code. If a structure appears arbitrary, others will feel empowered to change it.
+ + +
Be Precise
When you make a decision in your code, make sure you make it precisely. Know why you have made it and how you will deal with any exceptions.
+ +
High Cohesion
Cohesion is the degree to which elements of a whole belong together. Methods and fields in a single class and classes of a component should have high cohesion. High cohesion in classes and components results in simpler, more easily understandable code structure and design.
Smaller classes are easier to grasp. Classes should be smaller than about 100 lines of code. Otherwise, it is hard to spot how the class does its job and it probably does more than a single job.
Change is Local
+ + +
When a software system has to be maintained, extended and changed for a long time, keeping change local reduces involved costs and risks. Keeping change local means that there are boundaries in the design which changes do not cross.
It is Easy to Remove
ONE SWITCH: There may be no more than one switch statement for a given type of selection. The cases in that switch statement must create polymorphic objects that take the place of other such switch statements in the rest of the system.
+ + + +
Symmetry / Analogy
Favour symmetric designs (e.g. Load Save) and designs that follow analogies (e.g. same design as found in .NET framework).
+ +
We normally build software by adding, extending or changing features. However, removing elements is important so that the overall design can be kept as simple as possible. When a block gets too complicated, it has to be removed and replaced with one or more simpler blocks.
+ + +
Misplaced Responsibility
Something put in the wrong place.
Encodings in Names
No prefixes, no type/scope information
Understandability Consistency
If you do something a certain way, do all similar things in the same way: same variable name for same concepts, same naming pattern for corresponding concepts.
+ +
From Legacy Code to Clean Code Always have a Running System 1) Identify Features
+ +
Change your system in small steps, from a running state to a running state. Identify the existing features in your code and prioritise them according to how relevant they are for future development (likelihood and risk of change).
+ + +
Positive Conditionals
Positive conditionals are easier to read than negative conditionals.
Boundary conditions are hard to keep track of. Put the processing for them in one place, e.g. nextLevel = level + 1;
Two developers solving a problem together at a single workstation. One is the driver, the other is the navigator. The driver is responsible for writing the code. The navigator is responsible for keeping the solution aligned with the architecture, the coding guidelines and looks at where to go next (e.g. which test to write next). Both challenge their ideas and approaches to solutions.
Commit Reviews
A developer walks a peer developer through all code changes prior to committing (or pushing) the changes to the version control system. The peer developer checks the code against clean code guidelines and design guidelines.
Clutter
Code that is not dead but does not add any functionality
+ +
Inappropriate Information
Comment holding information better held in a different kind of system: product backlog, source control. Use code comments for technical notes only.
Coding Dojo
Comment does not add any value (redundant to code), is not well formed, not correct grammar/spelling.
4) Identify Components
Within a feature, identify the components used to provide the feature. Prioritise components according to relevance for future development (likelihood and risk of change).
In a Coding Dojo, a group of developers come together to exercise their skills. Two developers solve a problem (kata) in pair programming. The rest observe. After 10 minutes, the group rotates to build a new pair. The observers may critique the current solution, but only when all tests are green.
+ +
Bibliography
Clean Code: A Handbook of Agile Software Craftsmanship by Robert Martin
+ + + +
+ +
Exception Handling Catch Specific Exceptions Catch Where You Can React in a Meaningful Way
Catch exceptions as specific as possible. Catch only the exceptions for which you can react in a meaningful manner. Only catch exceptions when you can react in a meaningful way. Otherwise, let someone up in the call stack react to it.
Use ATDD and TDD (see Clean ATDD/TDD cheat sheet) to re-implement the component.
In an exceptional case, throw an exception when your method cannot do its job. Don't accept or return null. Don't return error codes. Exceptions should be thrown as early as possible after detecting an exceptional case. This helps to pinpoint the exact location of the problem by looking at the stack trace of the exception.
+ + + +
public int Foo(bool flag) Split method into several independent methods that can be called from the client without the flag.
First, isolate the code to be refactored from the rest. Then refactor. Finally, undo isolation. Move from one representation to another by temporary duplication of data structures.
Inappropriate Static
Static method that should be an instance method
Swallowing Exceptions
Exceptions can be swallowed only if the exceptional case is completely resolved after leaving the catch block. Otherwise, the system is left in an inconsistent state.
Refactor by introducing a temporary parallel implementation of an algorithm. Switch one caller after the other. Remove old solution when no longer needed.
Legend:
DO DONT +
Nested code should be more specific or handle less probable scenarios than unnested code.
Introduce an internal component boundary and push everything unwanted outside of the internal boundary into the demilitarized zone between component interface and internal boundary. Then refactor the component interface to match the internal boundary and eliminate the demilitarized zone.
+ + + +
+ + + + +
Faking Framework
Use a dynamic fake framework for fakes that show different behaviour in different test scenarios (little behaviour reuse).
Obsolete Test
A test that checks something no longer required in the system. May even prevent clean-up of production code because it is still referenced.
Write a unit test that reproduces the defect Fix code Test will succeed Defect will never return. Aka test after. Write unit tests to check existing code. You cannot and probably do not want to test drive everything. Use POUT to increase sanity. Use to add additional tests after TDDing (e.g. boundary cases).
Write a test that does not fully check the required behaviour, but brings you a step closer to it. Then use Extend Test below.
Use manually written fakes when they can be used in several tests and they have only little changed behaviour in these scenarios (behaviour reuse). Make sure that you follow the AAA (arrange, act, assert) syntax when using mocks. Dont mix setting up stubs (so that the testee can run) with expectations (on what the testee should do) in the same code block.
Another Test
If you think of new tests, then write them on the TO DO list and dont lose focus on current test.
The construction of dependencies and arguments used in calls to testee makes test hardly readable. Extract to helper methods that can be reused.
+ +
Learning Test
Write tests against external components to make sure they behave as expected.
Constructor Lifetime
Pass dependencies and configuration/parameters into the constructor that have a lifetime equal to or longer than the created object. For other values use methods or properties.
If your test needs a lot of mocks or mock setup, then consider splitting the testee into several classes or provide an additional abstraction between your testee and its dependencies.
+ + + +
Use abstraction layers at system boundaries (database, file system, web services, COM interfaces ...) that simplify unit testing by enabling the usage of mocks.
Erratic Test
Sometimes passes, sometimes fails due to left overs or environment.
+ + + + +
Isolated + + + + + + + + Repeatable
No assumed initial state, nothing left behind, no dependency on external services that might be unavailable (databases, file system ).
If the implementation is obvious then just implement it and see if test runs. If not, then step back and just get test running and refactor then.
Clear where the failure happened. No dependency between tests (random order).
Tiny Steps
Make tiny little steps. Add only a little code in test before writing the required production code. Then repeat. Add only one Assert per step.
+ + + +
Acceptance Test Driven Development Use Acceptance Tests to Drive Your TDD tests User Feature Test Automated ATDD Component Acceptance Tests
+ + + + +
Test Namespace
Put the tests in the same namespace as their associated testee.
Self-Validating
No manual test interpretation or intervention. Red or green!
Acceptance tests check for the required functionality. Let them guide your TDD. An acceptance test is a test for a complete user feature from top to bottom that provides business value. Use automated Acceptance Test Driven Development for regression testing and executable specifications. Write acceptance tests for individual components or subsystems so that these parts can be combined freely without losing test coverage.
Timely
Tests are written at the right time (TDD, DDT, POUTing)
Resource Files
Test and resource are together: FooTest.cs, FooTest.resx
A valid test that is, however, too large. Reasons can be that this test checks for more than one feature or the testee does more than one thing (violation of Single Responsibility Principle).
Naming Naming SUT Test Variables Naming Result Values Anonymous Variables
Checking Internals + + +
A test that accesses internals (private/protected members) of the testee directly (Reflection). This is a refactoring killer.
Simulate system boundaries like the user interface, databases, file system and external services to speed up your acceptance tests and to be able to check exceptional cases (e.g. a full hard disk). Use system tests to check the boundaries.
Give the variable holding the System Under Test always the same name (e.g. testee or sut). Clearly identifies the SUT, robust against refactoring. Give the variable holding the result of the tested method always the same name (e.g. result). Always use the same name for variables holding uninteresting arguments to tested methods (e.g. anonymousText).
Do not write acceptance tests for every possibility. Write acceptance tests only for real scenarios. The exceptional and theoretical cases can be covered more easily with unit tests.
Irrelevant Information
Test contains information that is not relevant to understand it.
Chatty Test
A test that fills the console with text probably used once to manually check for something.
These tests are brittle and refactoring killers. Test complete mini use cases in a way which reflects how the feature will be used in the real world. Do not test setters and getters in isolation, test the scenario they are used in.
Run all unit and acceptance tests covering currently worked on code prior to committing to the source code repository.
You need to build up knowledge to implement the acceptance test. Make an initial design
+ + +
Split the complete continuous integration workflow into individual stages to reduce feedback time.
Implement a Spike to gather enough knowledge so you can design a possible solution.
Roughly design how you want to implement the new functionality, especially the interface for your acceptance test (how to call and verify functionality).
Refactor Refactor existing code to simplify introduction of new functionality. Run all tests to keep code working. Succeeded, not all acceptance tests implemented yet Write an acceptance test Add arrange, act and assert parts to the acceptance test skeleton (Given, When, Then or Establish, Because, It ). Run acceptance test Failed Make error reason obvious The failing test should state what went wrong so you dont have to debug the code.
Continuous Deployment
Install the system to a test environment on every commit or manual request. Deployment to production environment is automated, too.
Test Pyramid
ATDD
You have no class design idea You have a class design idea Spike a solution Implement a Spike to get the acceptance test running so that you get an initial design. Make initial or update class design Design how you want to implement the new functionality. Do per class Succeeded, code clean, TO DO list empty
Bibliography
Test Driven Development: By Example by Kent Beck ATDD by Example: A Practical Guide to Acceptance Test-Driven Development by Markus Grtner The Art of Unit testing by Roy Osherove xUnit Test Patterns: Refactoring Test Code by Gerard Meszaros
TO DO list Add missing test when you think of one Remove test when written We write the TO DO list into the same file as the unit test with // TODO: Pick test: 1) Prove that the code is making a hard coded assumption. 2) Prove that something is wrong. 3) Prove that something is missing. Write a test
Add a minimal test or make a minimal change to an existing test (< 10 minutes).
Run test
TDD
Make error reason obvious The failing test should state what went wrong so you dont have to debug the code. Write code
Failed
Clean up code Succeeded, code not clean Apply clean code guidelines. Redesign classes as needed. (< 10 minutes).
Legend:
DO DONT +