My notes from this book:
Fixing the problem is the top priority., not finding out who caused it. Measuring compliance to process doesn’t measure outcome. Agile teams value outcome over process.
If you approach someone for help and get a less than professional response, you can try to salvage the conversation. Explain exactly what you want, and make it clear that your goal is the solution, not the blame/credit contest.
If one team member misunderstood a requirement, an API call, or the decisions reached in the last meeting, then it’s very likely other team members may have misunderstood as well. Make sure
the whole team is up to speed on the issue.
Ask a leading question that allows someone to figure out the problem for themselves.
If you’re having a design meeting, or are just having trouble getting to a solution, set a hard deadline such as lunchtime or the end of the day. That kind of time boxing helps keep the team moving and keeps you from getting too hung up on an endless ideological debate.
At the start of a meeting, pick a mediator who will act as the decision maker for that session.
Once a solution is picked (by whatever means), each team member should switch gears and give their complete cooperation in seeing it through to implementation.
Criticize ideas, not people. Take pride in arriving at a solution rather than proving whose idea is better.
Before setting out to find the best solution, it might be a good idea to make sure everyone agrees on what best means in this context.
Invest in team learning by picking up a topic, asking someone to present it and having discussion on its pros and cons and applicBility to your project. keep these sessíons shott but frequent so people can keep up. so stick to a regular schedule.
It is eaually impoftant to unlearn useless old habits and knowledge as it is to acquire new.
Keep asking Why. Don’t just accept what you’re told at face value. Keep questioning until you understand the root of the issue.
A hard deadline forces you to make the hard choices. You can’t waste time on philosophical discussions or features that are perpetually 80% done. A time box keeps you moving.
Developers, managers, or business analysts shouldn’t make business-critical decisions.
Present details to business owners in a language they can understand, and let them make the decision.
Design should only be as detailed as needed to implement
There are two levels of design: strategic and tactical. The up-front design is strategic: you typically do that when you don’t yet have a deep understanding of the requirements. That is, it should express a general strategy but not delve into precise details.
This up-front, strategic level of design shouldn’t specify the details of methods, parameters, fields, or the exact sequence of interaction between objects. That’s left to the tactical design, and it unfolds only as the project evolves
Instead of starting with a tactical design that focuses on individual methods or data types, it’s more appropriate to discuss possible class designs in terms of responsibilities, because that is still a high-level, goal-oriented approach. In fact, the CRC card design method does just that. Classes are described in terms of the following:
• Class name
• Responsibilities—what is it supposed to do
• Collaborators—what other objects it works with to get the job done
How can you tell whether a design is good or even adequate? The best feedback on the nature of design comes from the code. If small changes in requirements remain easy to implement, then it’s a good design. If small changes cause a large disruption or cause a disruption across a large swath of the code base, then the design needs improvement.
A good design is accurate, but not precise. That is, what it says should be correct, but it shouldn’t go far as to include details that might change or that are uncertain.
There’s a simple workflow to follow to make sure you don’t check in broken code:
Run your local tests. Begin by making sure the code you’re working on compiles and passes all of its unit tests. Then make sure all of the other tests in the system pass as well.
Check out the latest source. Get the latest copy of the source code from the version control system, and compile and test against that. Very often, this is where a surprise will show up: someone else may have made a change that’s incompatible with yours.
Check in. Now that you have the latest version of code compiling and passing its tests, you can check it in.
Deploy your application automatically from the start.
Use that deployment to install the application on arbitrary machines with different configurations to test dependencies. QA should test the deployment as well as your application.
Deploying an emergency bug fix should be easy, especially in a production server environment. You know it will happen, and you don’t want to have to do it manually, under pressure, at 3:30 a.m.
The user should always to be able to remove an installation safely and completely—especially in a QA environment.
Identify core features that’ll make the product usable, and get them into production—into the
hands of the real users—as soon as possible.
An alternative to fixed price contrcts:
1.Offer to build an initial, small, useful portion of the system (in
the construction analogy, perhaps just the garage). Pick a small enough set of features such that this first delivery should take no more than six to eight weeks. Explain that not all the features will make it in but that enough will be delivered so that the users could actually be productive.
2. At the end of that first iteration, the client has two choices: they can agree to continue to the next iteration, with the next set of features; or, they can cancel your contract, pay you only for the
few weeks worth of work you’ve done, and either throw it away or get some other group to take it and run with it.
3. If they go ahead, you’re in a better position to forecast what you can get done during the next iteration. At the end of the next iteration, the client still has those same two choices: stop now, or go on to the next.
Being agile doesn’t mean “Just start coding, and we’ll eventually know when we’re done.” You still need to give a ballpark estimate, with an explanation of how you arrived at it and the margin of
error given your current knowledge and assumptions
You might also consider a fixed price per iteration set in the contract while leaving the number of iterations loose, perhaps determined by ongoing work orders (a.k.a. “Statement of Work”).
Unit testing is only as effective as your test coverage. You might want to look at using test coverage tools to give you a rough idea of where you stand.
If your domain experts give you algorithms, calculations, or equations, provide them with a way of testing your implementation in isolation. Make those tests part of your test suite—you want to make sure you continue to provide the correct answers throughout the life of the project
Consider performance, convenience, productivity, cost, and time to market. If performance
is adequate, then focus on improving the other factors. Don’t complicate the design for the sake of perceived performance or elegance..
As the caller, you should not make decisions based on the state of the called object and then change the state of that object. The logic you are implementing should be the called object’s responsibility, not yours. For you to make decisions outside the object violates its encapsulation
and provides a fertile breeding ground for bugs.
A helpful side technique related to Tell, Don’t Ask is known as command-query separation. The idea is to categorize each of your functions and methods as either a command or a query and document them as such in the source code (it helps if all the commands are grouped together and all the queries are grouped together).
A routine acting as a command will likely change the state of the object and might also return some useful value as a convenience. A query just gives you information about the state of the object and does not modify the externally visible state of the object.
That is, queries should be side effect free as seen from the outside world (you may want to do some pre-calculation or caching behind the scenes as needed, but fetching the value of X in the object should not change the value of Y).
Tell, don’t ask. Don’t take on another object’s or component’s job. Tell it what to do, and stick to your own job.
Liskov’s Substitution principle tells us that “Any derived class object must be substitutable wherever a base class object is used, without the need for the user to know the difference.” In other words, code that uses methods in base classes must be able to use objects of derived classes without modification.
To comply with the Substitution principle, your derived class services (methods) should require no more, and promise no less, than the corresponding methods of the base class; it needs to be freely substitutable. This is an important consideration when designing class inheritance hierarchies.
When using inheritance, ask yourself whether your derived class is substitutable in place of the base class. If the answer is no, then ask yourself why you are using inheritance. If the answer is to reuse code in the base class when developing your new class, then you should probably use composition instead. Composition is where an object of your class contains and uses an object of another class, delegating responsibilities to the contained object (this technique is also known
as delegation).
Maintain a log of problems faced and solutions found.
Here are some items that you might want to include in your entries:
• Date of the problem
• Short description of the problem or issue
• Detailed description of the solution
• References to articles, and URLs, that have more details or related information
• Any code segments, settings, and snapshots of dialogs that may be part of the solution or help you further understand the details
Keep the log in a computer-searchable format. That way you can perform a keyword search to look up the details quickly.
Treat warnings as errors. Checking in code with warnings is just as bad as checking in code with errors or code that fails its tests. No checked-in code should produce any warnings from the build tools.
Emphasize collective ownership of code. Rotate developers across different modules and tasks in different areas of the system.
Set a time limit for how long anyone on the team can be stuck on a problem before asking for help. One hour seems to be a pretty good target.
What should you look for during a code review? You might develop your own list of specific issues to check (all exception handlers are nonempty, all database calls are made within the scope of a transaction, and so on), but here’s a very minimal list to get you started:
• Can you read and understand the code?
• Are there any obvious errors?
• Will the code have any undesirable effect on other parts of the application?
• Is there any duplication of code (within this section of code itself or with other parts of the system)?
• Are there any reasonable improvements or refactorings that can improve it?
In addition, you might want to consider using code analysis tools. If that sort of static analysis proves useful to you, make these tools part of your continuous build.
Review code after each task, using different developers.
Code reviews are useless unless you follow up on the recommendations quickly. You can schedule a follow-up meeting or use a system of code annotations to mark what needs to be done and track that it has been handled.
Always close the loop on code reviewers; let everyone know what steps you took as a result of the review.
Fixing the problem is the top priority., not finding out who caused it. Measuring compliance to process doesn’t measure outcome. Agile teams value outcome over process.
If you approach someone for help and get a less than professional response, you can try to salvage the conversation. Explain exactly what you want, and make it clear that your goal is the solution, not the blame/credit contest.
If one team member misunderstood a requirement, an API call, or the decisions reached in the last meeting, then it’s very likely other team members may have misunderstood as well. Make sure
the whole team is up to speed on the issue.
Ask a leading question that allows someone to figure out the problem for themselves.
If you’re having a design meeting, or are just having trouble getting to a solution, set a hard deadline such as lunchtime or the end of the day. That kind of time boxing helps keep the team moving and keeps you from getting too hung up on an endless ideological debate.
At the start of a meeting, pick a mediator who will act as the decision maker for that session.
Once a solution is picked (by whatever means), each team member should switch gears and give their complete cooperation in seeing it through to implementation.
Criticize ideas, not people. Take pride in arriving at a solution rather than proving whose idea is better.
Before setting out to find the best solution, it might be a good idea to make sure everyone agrees on what best means in this context.
Invest in team learning by picking up a topic, asking someone to present it and having discussion on its pros and cons and applicBility to your project. keep these sessíons shott but frequent so people can keep up. so stick to a regular schedule.
It is eaually impoftant to unlearn useless old habits and knowledge as it is to acquire new.
Keep asking Why. Don’t just accept what you’re told at face value. Keep questioning until you understand the root of the issue.
A hard deadline forces you to make the hard choices. You can’t waste time on philosophical discussions or features that are perpetually 80% done. A time box keeps you moving.
Developers, managers, or business analysts shouldn’t make business-critical decisions.
Present details to business owners in a language they can understand, and let them make the decision.
Design should only be as detailed as needed to implement
There are two levels of design: strategic and tactical. The up-front design is strategic: you typically do that when you don’t yet have a deep understanding of the requirements. That is, it should express a general strategy but not delve into precise details.
This up-front, strategic level of design shouldn’t specify the details of methods, parameters, fields, or the exact sequence of interaction between objects. That’s left to the tactical design, and it unfolds only as the project evolves
Instead of starting with a tactical design that focuses on individual methods or data types, it’s more appropriate to discuss possible class designs in terms of responsibilities, because that is still a high-level, goal-oriented approach. In fact, the CRC card design method does just that. Classes are described in terms of the following:
• Class name
• Responsibilities—what is it supposed to do
• Collaborators—what other objects it works with to get the job done
How can you tell whether a design is good or even adequate? The best feedback on the nature of design comes from the code. If small changes in requirements remain easy to implement, then it’s a good design. If small changes cause a large disruption or cause a disruption across a large swath of the code base, then the design needs improvement.
A good design is accurate, but not precise. That is, what it says should be correct, but it shouldn’t go far as to include details that might change or that are uncertain.
There’s a simple workflow to follow to make sure you don’t check in broken code:
Run your local tests. Begin by making sure the code you’re working on compiles and passes all of its unit tests. Then make sure all of the other tests in the system pass as well.
Check out the latest source. Get the latest copy of the source code from the version control system, and compile and test against that. Very often, this is where a surprise will show up: someone else may have made a change that’s incompatible with yours.
Check in. Now that you have the latest version of code compiling and passing its tests, you can check it in.
Deploy your application automatically from the start.
Use that deployment to install the application on arbitrary machines with different configurations to test dependencies. QA should test the deployment as well as your application.
Deploying an emergency bug fix should be easy, especially in a production server environment. You know it will happen, and you don’t want to have to do it manually, under pressure, at 3:30 a.m.
The user should always to be able to remove an installation safely and completely—especially in a QA environment.
Identify core features that’ll make the product usable, and get them into production—into the
hands of the real users—as soon as possible.
An alternative to fixed price contrcts:
1.Offer to build an initial, small, useful portion of the system (in
the construction analogy, perhaps just the garage). Pick a small enough set of features such that this first delivery should take no more than six to eight weeks. Explain that not all the features will make it in but that enough will be delivered so that the users could actually be productive.
2. At the end of that first iteration, the client has two choices: they can agree to continue to the next iteration, with the next set of features; or, they can cancel your contract, pay you only for the
few weeks worth of work you’ve done, and either throw it away or get some other group to take it and run with it.
3. If they go ahead, you’re in a better position to forecast what you can get done during the next iteration. At the end of the next iteration, the client still has those same two choices: stop now, or go on to the next.
Being agile doesn’t mean “Just start coding, and we’ll eventually know when we’re done.” You still need to give a ballpark estimate, with an explanation of how you arrived at it and the margin of
error given your current knowledge and assumptions
You might also consider a fixed price per iteration set in the contract while leaving the number of iterations loose, perhaps determined by ongoing work orders (a.k.a. “Statement of Work”).
Unit testing is only as effective as your test coverage. You might want to look at using test coverage tools to give you a rough idea of where you stand.
If your domain experts give you algorithms, calculations, or equations, provide them with a way of testing your implementation in isolation. Make those tests part of your test suite—you want to make sure you continue to provide the correct answers throughout the life of the project
Consider performance, convenience, productivity, cost, and time to market. If performance
is adequate, then focus on improving the other factors. Don’t complicate the design for the sake of perceived performance or elegance..
As the caller, you should not make decisions based on the state of the called object and then change the state of that object. The logic you are implementing should be the called object’s responsibility, not yours. For you to make decisions outside the object violates its encapsulation
and provides a fertile breeding ground for bugs.
A helpful side technique related to Tell, Don’t Ask is known as command-query separation. The idea is to categorize each of your functions and methods as either a command or a query and document them as such in the source code (it helps if all the commands are grouped together and all the queries are grouped together).
A routine acting as a command will likely change the state of the object and might also return some useful value as a convenience. A query just gives you information about the state of the object and does not modify the externally visible state of the object.
That is, queries should be side effect free as seen from the outside world (you may want to do some pre-calculation or caching behind the scenes as needed, but fetching the value of X in the object should not change the value of Y).
Tell, don’t ask. Don’t take on another object’s or component’s job. Tell it what to do, and stick to your own job.
Liskov’s Substitution principle tells us that “Any derived class object must be substitutable wherever a base class object is used, without the need for the user to know the difference.” In other words, code that uses methods in base classes must be able to use objects of derived classes without modification.
To comply with the Substitution principle, your derived class services (methods) should require no more, and promise no less, than the corresponding methods of the base class; it needs to be freely substitutable. This is an important consideration when designing class inheritance hierarchies.
When using inheritance, ask yourself whether your derived class is substitutable in place of the base class. If the answer is no, then ask yourself why you are using inheritance. If the answer is to reuse code in the base class when developing your new class, then you should probably use composition instead. Composition is where an object of your class contains and uses an object of another class, delegating responsibilities to the contained object (this technique is also known
as delegation).
Maintain a log of problems faced and solutions found.
Here are some items that you might want to include in your entries:
• Date of the problem
• Short description of the problem or issue
• Detailed description of the solution
• References to articles, and URLs, that have more details or related information
• Any code segments, settings, and snapshots of dialogs that may be part of the solution or help you further understand the details
Keep the log in a computer-searchable format. That way you can perform a keyword search to look up the details quickly.
Treat warnings as errors. Checking in code with warnings is just as bad as checking in code with errors or code that fails its tests. No checked-in code should produce any warnings from the build tools.
Emphasize collective ownership of code. Rotate developers across different modules and tasks in different areas of the system.
Set a time limit for how long anyone on the team can be stuck on a problem before asking for help. One hour seems to be a pretty good target.
What should you look for during a code review? You might develop your own list of specific issues to check (all exception handlers are nonempty, all database calls are made within the scope of a transaction, and so on), but here’s a very minimal list to get you started:
• Can you read and understand the code?
• Are there any obvious errors?
• Will the code have any undesirable effect on other parts of the application?
• Is there any duplication of code (within this section of code itself or with other parts of the system)?
• Are there any reasonable improvements or refactorings that can improve it?
In addition, you might want to consider using code analysis tools. If that sort of static analysis proves useful to you, make these tools part of your continuous build.
Review code after each task, using different developers.
Code reviews are useless unless you follow up on the recommendations quickly. You can schedule a follow-up meeting or use a system of code annotations to mark what needs to be done and track that it has been handled.
Always close the loop on code reviewers; let everyone know what steps you took as a result of the review.
No comments:
Post a Comment