├── Media
├── hand-1.png
├── test-covering.png
└── repository-logo.png
├── C#
├── [F]unctions
│ ├── [F02] Output Arguments.cs
│ ├── [F03] Flag Arguments.cs
│ └── [F01] Too Many Arguments.cs
├── [G]eneral
│ ├── [G29] Avoid Negative Conditionals.cs
│ ├── [G28] Encapsulate Conditionals.cs
│ ├── [G16] Obscured Intent.cs
│ ├── [G02] Obvious Behavior Is Unimplemented.cs
│ ├── [G33] Encapsulate Boundary Conditions.cs
│ ├── [G18] Inappropriate Static.cs
│ ├── [G19] Use Explanatory Variables.cs
│ ├── [G20] Function Names Should Say What They Do.cs
│ ├── [G05] Duplication.cs
│ ├── [G25] Replace Magic Numbers with Named Constants 2.cs
│ ├── [G32] Don`t Be Arbitrary.cs
│ ├── [G30] Functions Should Do One Thing.cs
│ ├── [G06] Code at Wrong Level of Abstraction.cs
│ ├── [G35] Keep Configurable Data at High Levels.cs
│ ├── [G04] Overridden Safeties.cs
│ ├── [G11] Inconsistency.cs
│ ├── [G25] Replace Magic Numbers with Named Constants 1.cs
│ ├── [G15] Selector Arguments.cs
│ ├── [G07] Base Classes Depending on Their Derivatives.cs
│ ├── [G31] Hidden Temporal Couplings.cs
│ ├── [G14] Feature Envy.cs
│ ├── [G23] Prefer Polymorphism to IfElse or SwitchCase.cs
│ ├── [G22] Make Logical Dependencies Physical.cs
│ └── [G34] Functions Should Descend Only One Level of Abstraction.cs
├── [C]omments
│ ├── [C01] Inappropriate Information.cs
│ ├── [C02] Obsolete Comment.cs
│ ├── [C05] Commented-Out Code.cs
│ ├── [C03] Redundant Comment.cs
│ └── [C04] Poorly Written Comment.cs
├── [N]ames
│ ├── [N07] Names Should Describe Side-Effects.cs
│ ├── [N05] Use Long Names for Long Scopes.cs
│ ├── [N04] Unambiguous Names.cs
│ ├── [N02] Choose Names at the Appropriate Level of Abstraction.cs
│ └── [N01] Choose Descriptive Names.cs
└── [J]ava and [C]sharp
│ ├── [J03] Constants versus Enums.cs
│ └── [J02] Don’t Inherit Constants.cs
├── Java
├── [G]eneral
│ ├── [G29] Avoid Negative Conditionals.java
│ ├── [G28] Encapsulate Conditionals.java
│ ├── [G16] Obscured Intent.java
│ ├── [G02] Obvious Behavior Is Unimplemented.java
│ ├── [G04] Overridden Safeties.java
│ ├── [G33] Encapsulate Boundary Conditions.java
│ ├── [G18] Inappropriate Static.java
│ ├── [G19] Use Explanatory Variables.java
│ ├── [G20] Function Names Should Say What They Do.java
│ ├── [G11] Inconsistency.java
│ ├── [G30] Functions Should Do One Thing.java
│ ├── [G25] Replace Magic Numbers with Named Constants 2.java
│ ├── [G05] Duplication.java
│ ├── [G32] Don`t Be Arbitrary.java
│ ├── [G06] Code at Wrong Level of Abstraction.java
│ ├── [G35] Keep Configurable Data at High Levels.java
│ ├── [G25] Replace Magic Numbers with Named Constants 1.java
│ ├── [G31] Hidden Temporal Couplings.java
│ ├── [G15] Selector Arguments.java
│ ├── [G07] Base Classes Depending on Their Derivatives.java
│ ├── [G14] Feature Envy.java
│ ├── [G23] Prefer Polymorphism to IfElse or SwitchCase.java
│ ├── [G22] Make Logical Dependencies Physical.java
│ └── [G34] Functions Should Descend Only One Level of Abstraction.java
├── [C]omments
│ ├── [C01] Inappropriate Information.java
│ ├── [C02] Obsolete Comment.java
│ ├── [C05] Commented-Out Code.java
│ ├── [C03] Redundant Comment.java
│ └── [C04] Poorly Written Comment.java
├── [N]ames
│ ├── [N07] Names Should Describe Side-Effects.java
│ ├── [N05] Use Long Names for Long Scopes.java
│ ├── [N04] Unambiguous Names.java
│ ├── [N02] Choose Names at the Appropriate Level of Abstraction.java
│ └── [N01] Choose Descriptive Names.java
├── [F]unctions
│ ├── [F02] Output Arguments.java
│ ├── [F01] Too Many Arguments.java
│ └── [F03] Flag Arguments.java
└── [J]ava and [C]sharp
│ ├── [J03] Constants versus Enums.java
│ └── [J02] Don’t Inherit Constants.java
├── LICENSE
├── CONTRIBUTING.md
└── README.md
/Media/hand-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SaintZet/HeuristicsForCleanCode/HEAD/Media/hand-1.png
--------------------------------------------------------------------------------
/Media/test-covering.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SaintZet/HeuristicsForCleanCode/HEAD/Media/test-covering.png
--------------------------------------------------------------------------------
/Media/repository-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SaintZet/HeuristicsForCleanCode/HEAD/Media/repository-logo.png
--------------------------------------------------------------------------------
/C#/[F]unctions/[F02] Output Arguments.cs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SaintZet/HeuristicsForCleanCode/HEAD/C#/[F]unctions/[F02] Output Arguments.cs
--------------------------------------------------------------------------------
/C#/[G]eneral/[G29] Avoid Negative Conditionals.cs:
--------------------------------------------------------------------------------
1 | if (buffer.ShouldCompact())
2 |
3 | //is preferable to
4 |
5 | if (!buffer.ShouldNotCompact())
6 |
--------------------------------------------------------------------------------
/Java/[G]eneral/[G29] Avoid Negative Conditionals.java:
--------------------------------------------------------------------------------
1 | if (buffer.shouldCompact())
2 |
3 | //is preferable to
4 |
5 | if (!buffer.shouldNotCompact())
6 |
--------------------------------------------------------------------------------
/C#/[G]eneral/[G28] Encapsulate Conditionals.cs:
--------------------------------------------------------------------------------
1 | if (ShouldBeDeleted(timer))
2 |
3 | //is preferable to
4 |
5 | if (timer.HasExpired() && !timer.IsRecurrent())
6 |
--------------------------------------------------------------------------------
/Java/[G]eneral/[G28] Encapsulate Conditionals.java:
--------------------------------------------------------------------------------
1 | if (shouldBeDeleted(timer))
2 |
3 | //is preferable to
4 |
5 | if (timer.hasExpired() && !timer.isRecurrent())
6 |
--------------------------------------------------------------------------------
/C#/[G]eneral/[G16] Obscured Intent.cs:
--------------------------------------------------------------------------------
1 | // Here is the overTimePay function as it might have appeared:
2 | public int m_otCalc(int iThsWkd, int iThsRte)
3 | {
4 | return iThsWkd * iThsRte +
5 | (int)Math.Round(0.5 * iThsRte * Math.Max(0, iThsWkd - 400);
6 | }
7 | // Small and dense as this might appear, it’s also virtually impenetrable.
8 |
--------------------------------------------------------------------------------
/Java/[G]eneral/[G16] Obscured Intent.java:
--------------------------------------------------------------------------------
1 | // Here is the overTimePay function as it might have appeared:
2 | public int m_otCalc() {
3 | return iThsWkd * iThsRte +
4 | (int) Math.round(0.5 * iThsRte *
5 | Math.max(0, iThsWkd - 400)
6 | );
7 | }
8 | // Small and dense as this might appear, it’s also virtually impenetrable.
--------------------------------------------------------------------------------
/Java/[C]omments/[C01] Inappropriate Information.java:
--------------------------------------------------------------------------------
1 | // This is an example class that violates the rule of including meta-data in comments.
2 | // It contains information about the author and last modified date.
3 |
4 | /**
5 | Author: John Doe
6 | Last modified: 1/1/2022
7 | SPR #: 1234
8 | */
9 | public class ExampleClass {
10 | // Code implementation here...
11 | }
12 |
--------------------------------------------------------------------------------
/Java/[C]omments/[C02] Obsolete Comment.java:
--------------------------------------------------------------------------------
1 | // This is an example method with an obsolete comment.
2 | // The comment is outdated and no longer reflects the implementation.
3 |
4 | /**
5 | This method calculates the sum of two numbers.
6 | Note: Make sure to pass positive integers only.
7 | */
8 | public int calculateSum(int a, int b) {
9 | // Code implementation here...
10 | }
--------------------------------------------------------------------------------
/C#/[C]omments/[C01] Inappropriate Information.cs:
--------------------------------------------------------------------------------
1 | // This is an example class that violates the rule of including meta-data in comments.
2 | // It contains information about the author and last modified date.
3 |
4 | /**
5 | * Author: John Doe
6 | * Last modified: 1/1/2022
7 | * SPR #: 1234
8 | */
9 | public class ExampleClass
10 | {
11 | // Code implementation here...
12 | }
--------------------------------------------------------------------------------
/C#/[G]eneral/[G02] Obvious Behavior Is Unimplemented.cs:
--------------------------------------------------------------------------------
1 | // A function that translates the name of a day to an enum that represents the day.
2 |
3 | Day day = DayDate.StringToDay(string dayName);
4 |
5 | // We would expect the string "Monday" to be translated to Day.MONDAY.
6 | // We would also expect the common abbreviations to be translated, and we would expect the function to ignore case.
7 |
--------------------------------------------------------------------------------
/Java/[G]eneral/[G02] Obvious Behavior Is Unimplemented.java:
--------------------------------------------------------------------------------
1 | // A function that translates the name of a day to an enum that represents the day.
2 |
3 | Day day = DayDate.StringToDay(String dayName);
4 |
5 | // We would expect the string "Monday" to be translated to Day.MONDAY.
6 | // We would also expect the common abbreviations to be translated, and we would expect the function to ignore case.
7 |
--------------------------------------------------------------------------------
/C#/[C]omments/[C02] Obsolete Comment.cs:
--------------------------------------------------------------------------------
1 | // This is an example method with an obsolete comment.
2 | // The comment is outdated and no longer reflects the implementation.
3 |
4 | /**
5 | * This method calculates the sum of two numbers.
6 | * Note: Make sure to pass positive integers only.
7 | */
8 | public int CalculateSum(int a, int b)
9 | {
10 | // Code implementation here...
11 | }
12 |
--------------------------------------------------------------------------------
/C#/[N]ames/[N07] Names Should Describe Side-Effects.cs:
--------------------------------------------------------------------------------
1 | public ObjectOutputStream GetOos()
2 | {
3 | if (_oos == null) {
4 | _oos = new ObjectOutputStream(_socket.GetOutputStream());
5 | }
6 |
7 | return _oos;
8 | }
9 | // This function does a bit more than get an "oos";
10 | // it creates the "oos" if it hasn't been created already.
11 | // Thus, a better name might be CreateOrReturnOos.
--------------------------------------------------------------------------------
/Java/[N]ames/[N07] Names Should Describe Side-Effects.java:
--------------------------------------------------------------------------------
1 | public ObjectOutputStream getOos() throws IOException {
2 | if (m_oos == null) {
3 | m_oos = new ObjectOutputStream(m_socket.getOutputStream());
4 | }
5 | return m_oos;
6 | }
7 | // This function does a bit more than get an "oos";
8 | // it creates the "oos" if it hasn't been created already.
9 | // Thus, a better name might be createOrReturnOos.
--------------------------------------------------------------------------------
/Java/[C]omments/[C05] Commented-Out Code.java:
--------------------------------------------------------------------------------
1 | // This is an example of commented-out code.
2 | // It is unclear why this code was commented out, how old it is, or if it is still relevant.
3 |
4 | /**
5 | This method does something useful.
6 | */
7 | public void someMethod() {
8 | //int result = doSomething();
9 | //System.out.println("The result is: " + result);
10 |
11 | int result = doSomethingNew();
12 | System.out.println(result + " - it's result");
13 | }
--------------------------------------------------------------------------------
/C#/[C]omments/[C05] Commented-Out Code.cs:
--------------------------------------------------------------------------------
1 | // This is an example of commented-out code.
2 | // It is unclear why this code was commented out, how old it is, or if it is still relevant.
3 |
4 | /**
5 | * This method does something useful.
6 | */
7 | public void SomeMethod()
8 | {
9 | //int result = DoSomething();
10 | //Console.WriteLine("The result is: " + result);
11 |
12 | int result = DoSomethingNew();
13 | Console.WriteLine(result + " - it's result");
14 | }
--------------------------------------------------------------------------------
/C#/[G]eneral/[G33] Encapsulate Boundary Conditions.cs:
--------------------------------------------------------------------------------
1 | if(level + 1 < tags.Length)
2 | {
3 | parts = new Parse(body, tags, level + 1, offset + endTag);
4 | body = null;
5 | }
6 | // Notice that level+1 appears twice.
7 | // This is a boundary condition that should be encapsulated within a variable named something like nextLevel.
8 |
9 | int nextLevel = level + 1;
10 | if(nextLevel < tags.Length)
11 | {
12 | parts = new Parse(body, tags, nextLevel, offset + endTag);
13 | body = null;
14 | }
15 |
--------------------------------------------------------------------------------
/Java/[G]eneral/[G04] Overridden Safeties.java:
--------------------------------------------------------------------------------
1 | java.io.Serializable:
2 |
3 | import java.io.Serializable;
4 |
5 | @SuppressWarnings("serial")
6 | public class MyClass implements Serializable {
7 | // fields, constructors and class methods
8 | }
9 | // Generally, using @SuppressWarnings should only be used when you are certain that
10 | // a compiler warning is a false positive and cannot be corrected in a more correct way.
11 | // The use of @SuppressWarnings should be limited and only used where necessary.
--------------------------------------------------------------------------------
/Java/[G]eneral/[G33] Encapsulate Boundary Conditions.java:
--------------------------------------------------------------------------------
1 | if(level + 1 < tags.length)
2 | {
3 | parts = new Parse(body, tags, level + 1, offset + endTag);
4 | body = null;
5 | }
6 | // Notice that level+1 appears twice.
7 | // This is a boundary condition that should be encapsulated within a variable named something like nextLevel.
8 |
9 | int nextLevel = level + 1;
10 | if(nextLevel < tags.length)
11 | {
12 | parts = new Parse(body, tags, nextLevel, offset + endTag);
13 | body = null;
14 | }
15 |
--------------------------------------------------------------------------------
/C#/[C]omments/[C03] Redundant Comment.cs:
--------------------------------------------------------------------------------
1 | // This is an example method with a redundant comment.
2 | // The comment repeats what the method name already says.
3 |
4 | ///
5 | /// This method returns the sum of two numbers.
6 | ///
7 | /// Sell request
8 | /// Sell response
9 | public SellResponse BeginSellItem(SellRequest sellRequest)
10 | {
11 | // Code implementation here...
12 | // throw new ManagedComponentException();
13 | }
14 |
--------------------------------------------------------------------------------
/C#/[N]ames/[N05] Use Long Names for Long Scopes.cs:
--------------------------------------------------------------------------------
1 | // Consider this snippet from the old standard "Bowling Game":
2 |
3 | private void RollMany(int n, int pins)
4 | {
5 | for (int i=0; i 0)
2 | Console.WriteLine("The number is positive");
3 | else
4 | Console.WriteLine("The number is negative");
5 | ...
6 | if (y > 0)
7 | Console.WriteLine("The number is positive");
8 | else
9 | Console.WriteLine("The number is negative");
10 |
11 | // In this example, the code for checking the sign of a number is duplicated twice.
12 | // This code can be improved by combining it into a single method and calling it at the right places in the program.
13 | // Here is an example of improved code:
14 |
15 | CheckNumberSign(x);
16 | ...
17 | CheckNumberSign(y);
18 |
19 | private static void CheckNumberSign(int number)
20 | {
21 | if (number > 0)
22 | Console.WriteLine("The number is positive");
23 | else
24 | Console.WriteLine("The number is negative");
25 | }
--------------------------------------------------------------------------------
/Java/[N]ames/[N04] Unambiguous Names.java:
--------------------------------------------------------------------------------
1 | private String doRename() throws Exception
2 | {
3 | if(refactorReferences)
4 | renameReferences();
5 | renamePage();
6 | pathToRename.removeNameFromEnd();
7 | pathToRename.addNameToEnd(newName);
8 | return PathParser.render(pathToRename);
9 | }
10 | // The name of this function does not say what the function does except in broad and vague terms.
11 | // This is emphasized by the fact that there is a function named renamePage inside the function named doRename!
12 | // What do the names tell you about the difference between the two functions? Nothing.
13 |
14 | // A better name for that function is renamePageAndOptionallyAllReferences.
15 | // This may seem long, and it is, but it's only called from one place in the module, so it's explanatory value outweighs the length.
--------------------------------------------------------------------------------
/Java/[G]eneral/[G30] Functions Should Do One Thing.java:
--------------------------------------------------------------------------------
1 | public void pay() {
2 | for (Employee e : employees) {
3 | if (e.isPayday()) {
4 | Money pay = e.calculatePay();
5 | e.deliverPay(pay);
6 | }
7 | }
8 | }
9 |
10 | // This bit of code does three things. It loops over all the employees, checks to see whether each employee ought to be paid,
11 | // and then pays the employee. This code would be better written as:
12 |
13 | public void pay() {
14 | for (Employee e : employees)
15 | payIfNecessary(e);
16 | }
17 | private void payIfNecessary(Employee e) {
18 | if (e.isPayday())
19 | calculateAndDeliverPay(e);
20 | }
21 | private void calculateAndDeliverPay(Employee e) {
22 | Money pay = e.calculatePay();
23 | e.deliverPay(pay);
24 | }
25 |
26 | // Each of these functions does one thing.
27 |
--------------------------------------------------------------------------------
/C#/[G]eneral/[G25] Replace Magic Numbers with Named Constants 2.cs:
--------------------------------------------------------------------------------
1 | assertEquals(7777, Employee.find(“John Doe”).employeeNumber());
2 |
3 | // There are two magic numbers in this assertion. The first is obviously 7777, though what it might mean is not obvious.
4 | // The second magic number is "John Doe" and again the intent is not clear.
5 |
6 | // It turns out that "John Doe" is the name of employee #7777 in a well-known test database created by our team.
7 | // Everyone in the team knows that when you connect to this database,
8 | // it will have several employees already cooked into it with well-known values and attributes.
9 | // It also turns out that "John Doe" represents the sole hourly employee in that test database. So this test should really read:
10 |
11 | assertEquals(
12 | HOURLY_EMPLOYEE_ID,
13 | Employee.find(HOURLY_EMPLOYEE_NAME).employeeNumber());
14 |
--------------------------------------------------------------------------------
/Java/[G]eneral/[G25] Replace Magic Numbers with Named Constants 2.java:
--------------------------------------------------------------------------------
1 | assertEquals(7777, Employee.find(“John Doe”).employeeNumber());
2 |
3 | // There are two magic numbers in this assertion. The first is obviously 7777, though what it might mean is not obvious.
4 | // The second magic number is "John Doe" and again the intent is not clear.
5 |
6 | // It turns out that "John Doe" is the name of employee #7777 in a well-known test database created by our team.
7 | // Everyone in the team knows that when you connect to this database,
8 | // it will have several employees already cooked into it with well-known values and attributes.
9 | // It also turns out that "John Doe" represents the sole hourly employee in that test database. So this test should really read:
10 |
11 | assertEquals(
12 | HOURLY_EMPLOYEE_ID,
13 | Employee.find(HOURLY_EMPLOYEE_NAME).employeeNumber());
14 |
--------------------------------------------------------------------------------
/C#/[G]eneral/[G32] Don`t Be Arbitrary.cs:
--------------------------------------------------------------------------------
1 | public class AliasLinkWidget : ParentWidget
2 | {
3 | public static class VariableExpandingWidgetRoot {
4 | ...
5 | }
6 | // The problem with this was that VariableExpandingWidgetRoot had no need to be inside the scope of AliasLinkWidget.
7 | // Moreover, other unrelated classes made use of AliasLinkWidget.VariableExpandingWidgetRoot.
8 | // These classes had no need to know about AliasLinkWidget.
9 |
10 | // Perhaps the programmer had plopped the VariableExpandingWidgetRoot into AliasWidget as a matter of convenience,
11 | // or perhaps he thought it really needed to be scoped inside AliasWidget. Whatever the reason, the result wound up being arbitrary.
12 | // Public classes that are not utilities of some other class should not be scoped inside another class.
13 | // The convention is to make them public at the top level of their package.
14 |
--------------------------------------------------------------------------------
/Java/[G]eneral/[G05] Duplication.java:
--------------------------------------------------------------------------------
1 | if (x > 0) {
2 | System.out.println("The number is positive");
3 | } else {
4 | System.out.println("The number is negative");
5 | }
6 |
7 | ...
8 |
9 | if (y > 0) {
10 | System.out.println("The number is positive");
11 | } else {
12 | System.out.println("The number is negative");
13 | }
14 |
15 | // In this example, the code for checking the sign of a number is duplicated twice.
16 | // This code can be improved by combining it into a single method and calling it at the right places in the program.
17 | // Here is an example of improved code:
18 |
19 | checkNumberSign(x);
20 | ...
21 | checkNumberSign(y);
22 |
23 | private static void checkNumberSign(int number) {
24 | if (number > 0) {
25 | System.out.println("The number is positive");
26 | } else {
27 | System.out.println("The number is negative");
28 | }
29 | }
--------------------------------------------------------------------------------
/Java/[G]eneral/[G32] Don`t Be Arbitrary.java:
--------------------------------------------------------------------------------
1 | public class AliasLinkWidget extends ParentWidget
2 | {
3 | public static class VariableExpandingWidgetRoot {
4 | ...
5 | }
6 | // The problem with this was that VariableExpandingWidgetRoot had no need to be inside the scope of AliasLinkWidget.
7 | // Moreover, other unrelated classes made use of AliasLinkWidget.VariableExpandingWidgetRoot.
8 | // These classes had no need to know about AliasLinkWidget.
9 |
10 | // Perhaps the programmer had plopped the VariableExpandingWidgetRoot into AliasWidget as a matter of convenience,
11 | // or perhaps he thought it really needed to be scoped inside AliasWidget. Whatever the reason, the result wound up being arbitrary.
12 | // Public classes that are not utilities of some other class should not be scoped inside another class.
13 | // The convention is to make them public at the top level of their package.
14 |
--------------------------------------------------------------------------------
/C#/[G]eneral/[G30] Functions Should Do One Thing.cs:
--------------------------------------------------------------------------------
1 | public void Pay()
2 | {
3 | foreach (Employee e in _employees)
4 | {
5 | if (e.isPayday())
6 | {
7 | Money pay = e.CalculatePay();
8 | e.DeliverPay(pay);
9 | }
10 | }
11 | }
12 |
13 | // This bit of code does three things. It loops over all the employees, checks to see whether each employee ought to be paid,
14 | // and then pays the employee. This code would be better written as:
15 |
16 | public void Pay()
17 | {
18 | foreach (Employee e in _employees)
19 | PayIfNecessary(e);
20 | }
21 |
22 | private void PayIfNecessary(Employee e)
23 | {
24 | if (e.IsPayday())
25 | CalculateAndDeliverPay(e);
26 | }
27 |
28 | private void CalculateAndDeliverPay(Employee e)
29 | {
30 | Money pay = e.CalculatePay();
31 | e.DeliverPay(pay);
32 | }
33 |
34 | // Each of these functions does one thing.
35 |
--------------------------------------------------------------------------------
/C#/[G]eneral/[G06] Code at Wrong Level of Abstraction.cs:
--------------------------------------------------------------------------------
1 | public interface IStack
2 | {
3 | object Pop();
4 | void Push(object o);
5 | double PercentFull();
6 | class EmptyException : Exception { }
7 | class FullException : Exception { }
8 | }
9 |
10 | // The PercentFull function is at the wrong level of abstraction.
11 | // Although there are many implementations of Stack where the concept of fullness is reasonable,
12 | // there are other implementations that simply could not know how full they are.
13 | // So the function would be better placed in a derivative interface such as BoundedStack.
14 |
15 | // Perhaps you are thinking that the implementation could just return zero if the stack were boundless.
16 | // The problem with that is that no stack is truly boundless.
17 | // You cannot really prevent an OutOfMemoryException by checking for
18 |
19 | stack.PercentFull() < 50.0.
20 |
21 | // Implementing the function to return 0 would be telling a lie.
22 |
--------------------------------------------------------------------------------
/C#/[F]unctions/[F03] Flag Arguments.cs:
--------------------------------------------------------------------------------
1 | public void SendMessage(string message, string recipient, bool isUrgent, bool isPrivate, bool isEncrypted, bool isSigned)
2 | {
3 | // message sending code
4 | }
5 |
6 | // It is possible to divide this method into several smaller and specialized ones.
7 |
8 | // This approach will improve the readability and maintainability of the code,
9 | // as each method will perform only one specific operation, and their names will reflect this operation.
10 | // In addition, this will avoid the need to pass multiple arguments to methods,
11 | // which will reduce the likelihood of errors and simplify code testing and debugging.
12 |
13 | public void SendMessage(Message message, string recipient)
14 | {
15 | // message sending code
16 | }
17 |
18 | public class Message
19 | {
20 | public void MarkUrgent() { ... }
21 |
22 | public void MarkPrivate() { ... }
23 |
24 | public void Encrypt() { ... }
25 |
26 | public void Sign(string signatory) { .. }
27 | }
--------------------------------------------------------------------------------
/Java/[J]ava and [C]sharp/[J03] Constants versus Enums.java:
--------------------------------------------------------------------------------
1 | public class HourlyEmployee extends Employee {
2 | private int tenthsWorked;
3 | HourlyPayGrade grade;
4 | public Money calculatePay() {
5 | int straightTime = Math.min(tenthsWorked, TENTHS_PER_WEEK);
6 | int overTime = tenthsWorked - straightTime;
7 | return new Money(
8 | grade.rate() * (tenthsWorked + OVERTIME_RATE * overTime)
9 | );
10 | }
11 | ...
12 | }
13 |
14 | public enum HourlyPayGrade {
15 | APPRENTICE {
16 | public double rate() {
17 | return 1.0;
18 | }
19 | },
20 | LEUTENANT_JOURNEYMAN {
21 | public double rate() {
22 | return 1.2;
23 | }
24 | },
25 | JOURNEYMAN {
26 | public double rate() {
27 | return 1.5;
28 | }
29 | },
30 | MASTER {
31 | public double rate() {
32 | return 2.0;
33 | }
34 | };
35 | public abstract double rate();
36 | }
--------------------------------------------------------------------------------
/C#/[G]eneral/[G35] Keep Configurable Data at High Levels.cs:
--------------------------------------------------------------------------------
1 | public static void Main(String[] args)
2 | {
3 | Arguments arguments = ParseCommandLine(args);
4 | ...
5 | }
6 | public class Arguments
7 | {
8 | public static const string DEFAULT_PATH = ".";
9 | public static const string DEFAULT_ROOT = "FitNesseRoot";
10 | public static const int DEFAULT_PORT = 80;
11 | public static const int DEFAULT_VERSION_DAYS = 14;
12 | ...
13 | }
14 | // The command-line arguments are parsed in the very first executable line.
15 | // The default values of those arguments are specified at the top of the Argument class.
16 | // You don’t have to go looking in low levels of the system for statements like this one:
17 |
18 | if (arguments.port == 0) // use 80 by default
19 |
20 | //The configuration constants reside at a very high level and are easy to change.
21 | //They get passed down to the rest of the application.
22 | //The lower levels of the application do not own the values of these constants.
23 |
--------------------------------------------------------------------------------
/Java/[G]eneral/[G06] Code at Wrong Level of Abstraction.java:
--------------------------------------------------------------------------------
1 | public interface Stack {
2 | Object pop() throws EmptyException;
3 | void push(Object o) throws FullException;
4 | double percentFull();
5 | class EmptyException extends Exception {}
6 | class FullException extends Exception {}
7 | }
8 | // The percentFull function is at the wrong level of abstraction.
9 | // Although there are many implementations of Stack where the concept of fullness is reasonable,
10 | // there are other implementations that simply could not know how full they are.
11 | // So the function would be better placed in a derivative interface such as BoundedStack.
12 |
13 | // Perhaps you are thinking that the implementation could just return zero if the stack were boundless.
14 | // The problem with that is that no stack is truly boundless.
15 | // You cannot really prevent an OutOfMemoryException by checking for
16 |
17 | stack.percentFull() < 50.0.
18 |
19 | // Implementing the function to return 0 would be telling a lie.
--------------------------------------------------------------------------------
/Java/[G]eneral/[G35] Keep Configurable Data at High Levels.java:
--------------------------------------------------------------------------------
1 | public static void main(String[] args) throws Exception
2 | {
3 | Arguments arguments = parseCommandLine(args);
4 | ...
5 | }
6 | public class Arguments
7 | {
8 | public static final String DEFAULT_PATH = ".";
9 | public static final String DEFAULT_ROOT = "FitNesseRoot";
10 | public static final int DEFAULT_PORT = 80;
11 | public static final int DEFAULT_VERSION_DAYS = 14;
12 | ...
13 | }
14 | // The command-line arguments are parsed in the very first executable line.
15 | // The default values of those arguments are specified at the top of the Argument class.
16 | // You don’t have to go looking in low levels of the system for statements like this one:
17 |
18 | if (arguments.port == 0) // use 80 by default
19 |
20 | //The configuration constants reside at a very high level and are easy to change.
21 | //They get passed down to the rest of the application.
22 | //The lower levels of the application do not own the values of these constants.
23 |
--------------------------------------------------------------------------------
/C#/[F]unctions/[F01] Too Many Arguments.cs:
--------------------------------------------------------------------------------
1 | // In this example, the SendMessage() method takes 6 arguments, which can cause problems when reading and using the code.
2 | // In addition, these arguments do not give a clear understanding of what is happening inside the method.
3 | public void SendMessage(string message, string recipient, bool isUrgent, bool isPrivate, bool isEncrypted, bool isSigned)
4 | {
5 | // message sending code
6 | }
7 |
8 | // Instead, we could use an object to group these arguments:
9 | public class Message
10 | {
11 | private string message;
12 | private bool isUrgent;
13 | private bool isPrivate;
14 | private bool isEncrypted;
15 | private bool isSigned;
16 |
17 | // getters and setters for all fields
18 | }
19 |
20 | public void SendMessage(Message message, string recipient)
21 | {
22 | // message sending code
23 | }
24 |
25 | // This approach makes the code more readable and understandable,
26 | // since we have only one argument that contains all the necessary data for sending a message.
--------------------------------------------------------------------------------
/Java/[F]unctions/[F01] Too Many Arguments.java:
--------------------------------------------------------------------------------
1 | // In this example, the SendMessage() method takes 6 arguments, which can cause problems when reading and using the code.
2 | // In addition, these arguments do not give a clear understanding of what is happening inside the method.
3 | public void sendMessage(String message, String recipient, boolean isUrgent, boolean isPrivate, boolean isEncrypted, boolean isSigned) {
4 | // message sending code
5 | }
6 |
7 | // Instead, we could use an object to group these arguments:
8 | public class Message {
9 | private String message;
10 | private boolean isUrgent;
11 | private boolean isPrivate;
12 | private boolean isEncrypted;
13 | private boolean isSigned;
14 |
15 | // getters and setters for all fields
16 | }
17 |
18 | public void sendMessage(Message message, String recipient) {
19 | // message sending code
20 | }
21 |
22 | // This approach makes the code more readable and understandable,
23 | // since we have only one argument that contains all the necessary data for sending a message.
--------------------------------------------------------------------------------
/Java/[F]unctions/[F03] Flag Arguments.java:
--------------------------------------------------------------------------------
1 | public void sendMessage(String message, String recipient, boolean isUrgent, boolean isPrivate, boolean isEncrypted, boolean isSigned){
2 | // message sending code
3 | }
4 |
5 | // It is possible to divide this method into several smaller and specialized ones.
6 |
7 | // This approach will improve the readability and maintainability of the code,
8 | // as each method will perform only one specific operation, and their names will reflect this operation.
9 | // In addition, this will avoid the need to pass multiple arguments to methods,
10 | // which will reduce the likelihood of errors and simplify code testing and debugging.
11 |
12 | public void SendMessage(Message message, String recipient){
13 | // message sending code
14 | }
15 |
16 | public class Message{
17 | public void markUrgent(){
18 | ...
19 | }
20 |
21 | public void markPrivate(){
22 | ...
23 | }
24 |
25 | public void encrypt(){
26 | ...
27 | }
28 |
29 | public void sign(string signatory){
30 | ...
31 | }
32 | }
--------------------------------------------------------------------------------
/C#/[G]eneral/[G04] Overridden Safeties.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | class Program
4 | {
5 | static void Main(string[] args)
6 | {
7 | #pragma warning disable // Disable all compiler warnings
8 |
9 | // Code that may contain compiler warnings
10 | int unusedVariable; // Variable is not used
11 |
12 | #pragma warning restore // Turn on all compiler warnings
13 |
14 | Console.WriteLine("The program has ended.");
15 | }
16 | }
17 |
18 | // In this example, we have used the #pragma warning disable directive without specifying a specific warning code.
19 | // This will disable all compiler warnings for the block of code following the directive.
20 | // We then enabled all compiler warnings with the #pragma warning restore directive.
21 | // This ensures that all compiler warnings are enabled after the block of code where we disabled them.
22 |
23 | // As a reminder, disabling compiler warnings should be used with caution,
24 | // and only in cases where it is absolutely necessary,
25 | // such as when you are working with old code that does not meet modern standards and compiler requirements.
26 |
--------------------------------------------------------------------------------
/C#/[G]eneral/[G11] Inconsistency.cs:
--------------------------------------------------------------------------------
1 | // If within a particular function you use a variable named response to hold an HttpResponse ,
2 | // then use the same variable name consistently in the other functions that use HttpResponse objects.
3 | // If you name a method processVerificationRequest, then use a similar name,
4 | // such as processDeletionRequest, for the methods that process other kinds of requests.
5 | public class ExampleClass
6 | {
7 | private HttpResponse response;
8 |
9 | public void ProcessVerificationRequest()
10 | {
11 | // Use the response variable consistently throughout this function
12 | response.SendVerificationResponse();
13 | }
14 |
15 | public void ProcessDeletionRequest()
16 | {
17 | // Use the response variable consistently throughout this function
18 | response.SendDeletionResponse();
19 | }
20 |
21 | // Use a similar naming convention for methods that process other kinds of requests
22 | public void ProcessUpdateRequest()
23 | {
24 | // Use the response variable consistently throughout this function
25 | response.SendUpdateResponse();
26 | }
27 | }
--------------------------------------------------------------------------------
/C#/[J]ava and [C]sharp/[J03] Constants versus Enums.cs:
--------------------------------------------------------------------------------
1 | public class HourlyEmployee : Employee
2 | {
3 | private int _tenthsWorked;
4 | HourlyPayGrade _grade;
5 |
6 | public Money CalculatePay()
7 | {
8 | int straightTime = Math.Min(_tenthsWorked, PayrollConstants.TENTHS_PER_WEEK);
9 | int overTime = _tenthsWorked - straightTime;
10 | return new Money(_grade.Rate() * (_tenthsWorked + PayrollConstants.OVERTIME_RATE * overTime));
11 | }
12 | }
13 |
14 | public enum HourlyPayGrade
15 | {
16 | APPRENTICE,
17 | LEUTENANT_JOURNEYMAN,
18 | JOURNEYMAN,
19 | MASTER
20 | }
21 |
22 | public static class HourlyPayGradeExtensions
23 | {
24 | public static double Rate(this HourlyPayGrade grade)
25 | {
26 | switch (grade)
27 | {
28 | case HourlyPayGrade.APPRENTICE:
29 | return 1.0;
30 | case HourlyPayGrade.LEUTENANT_JOURNEYMAN:
31 | return 1.2;
32 | case HourlyPayGrade.JOURNEYMAN:
33 | return 1.5;
34 | case HourlyPayGrade.MASTER:
35 | return 2.0;
36 | default:
37 | throw new ArgumentException("Invalid hourly pay grade");
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/C#/[N]ames/[N02] Choose Names at the Appropriate Level of Abstraction.cs:
--------------------------------------------------------------------------------
1 | // Consider the Modem interface below:
2 |
3 | public interface Modem
4 | {
5 | bool Dial(string phoneNumber);
6 | bool Disconnect();
7 | bool Send(char c);
8 | char Recv();
9 | string GetConnectedPhoneNumber();
10 | }
11 | // At first this looks fine. The functions all seem appropriate.
12 | // Indeed, for many applications they are. But now consider an application in which some modems aren't connected by dialling.
13 | // Rather they are connected permanently by hard wiring them together
14 | // (think of the cable modems that provide Internet access to most homes nowadays).
15 | // Perhaps some are connected by sending a port number to a switch over a USB connection.
16 | // Clearly the notion of phone numbers is at the wrong level of abstraction. A better naming strategy for this scenario might be:
17 |
18 | public interface Modem
19 | {
20 | bool Connect(string connectionLocator);
21 | bool Disconnect();
22 | bool Send(char c);
23 | char Recv();
24 | string GetConnectedLocator();
25 | }
26 | // Now the names don't make any commitments about phone numbers.
27 | // They can still be used for phone numbers, or they could be used for any other kind of connection strategy.
--------------------------------------------------------------------------------
/Java/[N]ames/[N02] Choose Names at the Appropriate Level of Abstraction.java:
--------------------------------------------------------------------------------
1 | // Consider the Modem interface below:
2 |
3 | public interface Modem {
4 | boolean dial(String phoneNumber);
5 | boolean disconnect();
6 | boolean send(char c);
7 | char recv();
8 | String getConnectedPhoneNumber();
9 | }
10 | // At first this looks fine. The functions all seem appropriate.
11 | // Indeed, for many applications they are. But now consider an application in which some modems aren't connected by dialling.
12 | // Rather they are connected permanently by hard wiring them together
13 | // (think of the cable modems that provide Internet access to most homes nowadays).
14 | // Perhaps some are connected by sending a port number to a switch over a USB connection.
15 | // Clearly the notion of phone numbers is at the wrong level of abstraction. A better naming strategy for this scenario might be:
16 |
17 | public interface Modem {
18 | boolean connect(String connectionLocator);
19 | boolean disconnect();
20 | boolean send(char c);
21 | char recv();
22 | String getConnectedLocator();
23 | }
24 | // Now the names don't make any commitments about phone numbers.
25 | // They can still be used for phone numbers, or they could be used for any other kind of connection strategy.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/C#/[G]eneral/[G25] Replace Magic Numbers with Named Constants 1.cs:
--------------------------------------------------------------------------------
1 | double milesWalked = feetWalked/5280.0;
2 | int dailyPay = hourlyRate * 8;
3 | double circumference = radius * Math.PI * 2;
4 |
5 | // Do we really need the constants FEET_PER_MILE, WORK_HOURS_PER_DAY, and TWO in the above examples?
6 | // Clearly, the last case is absurd. There are some formulae in which constants are simply better written as raw numbers.
7 | // You might quibble about the WORK_HOURS_PER_DAY case because the laws or conventions might change.
8 | // On the other hand, that formula reads so nicely with the 8 in it that
9 | // I would be reluctant to add 17 extra characters to the readers’ burden.
10 | // And in the FEET_PER_MILE case, the number 5280 is so very well known and so unique a constant
11 | // that readers would recognize it even if it stood alone on a page with no context surrounding it.
12 |
13 | // Constants like 3.141592653589793 are also very well known and easily recognizable.
14 | // However, the chance for error is too great to leave them raw.
15 | // Every time someone sees 3.1415927535890793, they know that it is p, and so they fail to scrutinize it.
16 | // We also don’t want people using 3.14, 3.14159, 3.142, and so forth.
17 | // Therefore, it is a good thing that Math.PI has already been defined for us.
18 |
--------------------------------------------------------------------------------
/Java/[G]eneral/[G25] Replace Magic Numbers with Named Constants 1.java:
--------------------------------------------------------------------------------
1 | double milesWalked = feetWalked/5280.0;
2 | int dailyPay = hourlyRate * 8;
3 | double circumference = radius * Math.PI * 2;
4 |
5 | // Do we really need the constants FEET_PER_MILE, WORK_HOURS_PER_DAY, and TWO in the above examples?
6 | // Clearly, the last case is absurd. There are some formulae in which constants are simply better written as raw numbers.
7 | // You might quibble about the WORK_HOURS_PER_DAY case because the laws or conventions might change.
8 | // On the other hand, that formula reads so nicely with the 8 in it that
9 | // I would be reluctant to add 17 extra characters to the readers’ burden.
10 | // And in the FEET_PER_MILE case, the number 5280 is so very well known and so unique a constant
11 | // that readers would recognize it even if it stood alone on a page with no context surrounding it.
12 |
13 | // Constants like 3.141592653589793 are also very well known and easily recognizable.
14 | // However, the chance for error is too great to leave them raw.
15 | // Every time someone sees 3.1415927535890793, they know that it is p, and so they fail to scrutinize it.
16 | // We also don’t want people using 3.14, 3.14159, 3.142, and so forth.
17 | // Therefore, it is a good thing that Math.PI has already been defined for us.
18 |
--------------------------------------------------------------------------------
/Java/[G]eneral/[G31] Hidden Temporal Couplings.java:
--------------------------------------------------------------------------------
1 | public class MoogDiver {
2 | Gradient gradient;
3 | List splines;
4 | public void dive(String reason) {
5 | saturateGradient();
6 | reticulateSplines();
7 | diveForMoog(reason);
8 | }
9 | ...
10 | }
11 |
12 | //The order of the three functions is important. You must saturate the gradient before you can reticulate the splines,
13 | //and only then can you dive for the moog. Unfortunately, the code does not enforce this temporal coupling.
14 | //Another programmer could call reticulateSplines before saturateGradient was called, leading to an UnsaturatedGradientException.
15 |
16 | // A better solution is:
17 |
18 | public class MoogDiver {
19 | Gradient gradient;
20 | List splines;
21 | public void dive(String reason) {
22 | Gradient gradient = saturateGradient();
23 | List splines = reticulateSplines(gradient);
24 | diveForMoog(splines, reason);
25 | }
26 | ...
27 | }
28 |
29 | // This exposes the temporal coupling by creating a bucket brigade.
30 | // Each function produces a result that the next function needs, so there is no reasonable way to call them out of order.
31 |
32 | //You might complain that this increases the complexity of the functions, and you’d be right.
33 | //But that extra syntactic complexity exposes the true temporal complexity of the situation.
34 |
35 | //Note that I left the instance variables in place. I presume that they are needed by private methods in the class.
36 | //Even so, I want the arguments in place to make the temporal coupling explicit.
37 |
--------------------------------------------------------------------------------
/Java/[G]eneral/[G15] Selector Arguments.java:
--------------------------------------------------------------------------------
1 | public int calculateWeeklyPay(boolean overtime) {
2 | int tenthRate = getTenthRate();
3 | int tenthsWorked = getTenthsWorked();
4 | int straightTime = Math.min(400, tenthsWorked);
5 | int overTime = Math.max(0, tenthsWorked - straightTime);
6 | int straightPay = straightTime * tenthRate;
7 | double overtimeRate = overtime ? 1.5 : 1.0 * tenthRate;
8 | int overtimePay = (int)Math.round(overTime*overtimeRate);
9 | return straightPay + overtimePay;
10 | }
11 | // You call this function with a true if overtime is paid as time and a half, and with a false if overtime is paid as straight time.
12 | // It’s bad enough that you must remember what calculateWeeklyPay(false) means whenever you happen to stumble across it.
13 | // But the real shame of a function like this is that the author missed the opportunity to write the following:
14 |
15 | public int straightPay() {
16 | return getTenthsWorked() * getTenthRate();
17 | }
18 | public int overTimePay() {
19 | int overTimeTenths = Math.max(0, getTenthsWorked() - 400);
20 | int overTimePay = overTimeBonus(overTimeTenths);
21 | return straightPay() + overTimePay;
22 | }
23 | private int overTimeBonus(int overTimeTenths) {
24 | double bonus = 0.5 * getTenthRate() * overTimeTenths;
25 | return (int) Math.round(bonus);
26 | }
27 | // Of course, selectors need not be boolean.
28 | // They can be enums, integers, or any other type of argument that is used to select the behavior of the function.
29 | // In general it is better to have many functions than to pass some code into a function to select the behavior.
--------------------------------------------------------------------------------
/C#/[G]eneral/[G15] Selector Arguments.cs:
--------------------------------------------------------------------------------
1 | public int СalculateWeeklyPay(bool overtime)
2 | {
3 | int tenthRate = GetTenthRate();
4 | int tenthsWorked = GetTenthsWorked();
5 | int straightTime = Math.min(400, tenthsWorked);
6 | int overTime = Math.max(0, tenthsWorked - straightTime);
7 | int straightPay = straightTime * tenthRate;
8 | double overtimeRate = overtime ? 1.5 : 1.0 * tenthRate;
9 | int overtimePay = (int)Math.Round(overTime*overtimeRate);
10 |
11 | return straightPay + overtimePay;
12 | }
13 | // You call this function with a true if overtime is paid as time and a half, and with a false if overtime is paid as straight time.
14 | // It’s bad enough that you must remember what CalculateWeeklyPay(false) means whenever you happen to stumble across it.
15 | // But the real shame of a function like this is that the author missed the opportunity to write the following:
16 |
17 | public int StraightPay()
18 | {
19 | return GetTenthsWorked() * GetTenthRate();
20 | }
21 |
22 | public int OverTimePay()
23 | {
24 | int overTimeTenths = Math.max(0, GetTenthsWorked() - 400);
25 | int overTimePay = overTimeBonus(overTimeTenths);
26 |
27 | return straightPay() + overTimePay;
28 | }
29 |
30 | private int OverTimeBonus(int overTimeTenths)
31 | {
32 | double bonus = 0.5 * GetTenthRate() * overTimeTenths;
33 |
34 | return (int) Math.Round(bonus);
35 | }
36 | // Of course, selectors need not be boolean.
37 | // They can be enums, integers, or any other type of argument that is used to select the behavior of the function.
38 | // In general it is better to have many functions than to pass some code into a function to select the behavior.
39 |
--------------------------------------------------------------------------------
/C#/[G]eneral/[G07] Base Classes Depending on Their Derivatives.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | public class Animal
4 | {
5 | public void Feed(string foodType)
6 | {
7 | if (foodType == "meat")
8 | Console.WriteLine("Animal is eating meat");
9 | else if (foodType == "fish")
10 | Console.WriteLine("Animal is eating fish");
11 | }
12 | }
13 |
14 | public class Cat : Animal
15 | {
16 | public void Eat() =>
17 | Feed("fish");
18 | }
19 |
20 | public class Dog : Animal
21 | {
22 | public void Eat() =>
23 | Feed("meat");
24 | }
25 |
26 | // In this example, the "Animal" class contains a "Feed" method that accepts a food type and
27 | // prints out a message that the animal is eating.
28 | // The "Cat" and "Dog" classes then inherit from the "Animal" class and define their own "Eat" methods
29 | // that call the "Feed" method of the base class to feed the animal.
30 |
31 | // However, this violates the rule that base classes should not know about their derived classes.
32 | // To fix this, each derived class must define its own "Feed" method to implement the class-specific feeding logic.
33 |
34 | using System;
35 |
36 | public abstract class Animal
37 | {
38 | public abstract void Eat();
39 | }
40 |
41 | public class Cat : Animal
42 | {
43 | public override void Eat() =>
44 | Feed("fish");
45 |
46 | private void Feed(string foodType) =>
47 | Console.WriteLine("Cat is eating " + foodType);
48 | }
49 |
50 | public class Dog : Animal
51 | {
52 | public override void Eat() =>
53 | Feed("meat");
54 |
55 | private void Feed(string foodType) =>
56 | Console.WriteLine("Dog is eating " + foodType);
57 | }
--------------------------------------------------------------------------------
/C#/[J]ava and [C]sharp/[J02] Don’t Inherit Constants.cs:
--------------------------------------------------------------------------------
1 | public class HourlyEmployee : Employee
2 | {
3 | private int _tenthsWorked;
4 | private double _hourlyRate;
5 |
6 | public Money CalculatePay()
7 | {
8 | int straightTime = Math.min(_tenthsWorked, TENTHS_PER_WEEK);
9 | int overTime = _tenthsWorked - straightTime;
10 |
11 | return new Money(_hourlyRate * (tenthsWorked + OVERTIME_RATE * overTime));
12 | }
13 | ...
14 | }
15 | // Where did the constants TENTHS_PER_WEEK and OVERTIME_RATE come from?
16 | // They might have come from class Employee;
17 | // so let's take a look at that:
18 |
19 | public class Employee : PayrollConstants
20 | {
21 | public bool IsPayday();
22 | public Money CalculatePay();
23 | public void DeliverPay(Money pay);
24 | }
25 | // Nope, not there. But then where? Look closely at class Employee. It implements PayrollConstants.
26 |
27 | public class PayrollConstants
28 | {
29 | public static int TENTHS_PER_WEEK = 400;
30 | public static double OVERTIME_RATE = 1.5;
31 | }
32 | // This is a hideous practice! The constants are hidden at the top of the inheritance hierarchy.
33 | // Ick! Don't use inheritance as a way to cheat the scoping rules of the language. Use a static using instead.
34 |
35 | public class HourlyEmployee : Employee
36 | {
37 | private int _tenthsWorked;
38 | private double _hourlyRate;
39 |
40 | public Money CalculatePay()
41 | {
42 | int straightTime = Math.min(_tenthsWorked, PayrollConstants.TENTHS_PER_WEEK);
43 | int overTime = _tenthsWorked - straightTime;
44 | return new Money(_hourlyRate * (_tenthsWorked + OVERTIME_RATE * overTime));
45 | }
46 | ...
47 | }
--------------------------------------------------------------------------------
/C#/[G]eneral/[G31] Hidden Temporal Couplings.cs:
--------------------------------------------------------------------------------
1 | public class MoogDiver
2 | {
3 | private Gradient _gradient;
4 | private List _splines;
5 |
6 | public void Dive(string reason)
7 | {
8 | SaturateGradient();
9 | ReticulateSplines();
10 | DiveForMoog(reason);
11 | }
12 | ...
13 | }
14 |
15 | //The order of the three functions is important. You must saturate the gradient before you can reticulate the splines,
16 | //and only then can you dive for the moog. Unfortunately, the code does not enforce this temporal coupling.
17 | //Another programmer could call reticulateSplines before saturateGradient was called, leading to an UnsaturatedGradientException.
18 |
19 | // A better solution is:
20 |
21 | public class MoogDiver
22 | {
23 | private Gradient _gradient;
24 | private List _splines;
25 |
26 | public void dive(string reason)
27 | {
28 | Gradient _gradient = SaturateGradient();
29 | List _splines = ReticulateSplines(gradient);
30 | DiveForMoog(_splines, reason);
31 | }
32 | ...
33 | }
34 |
35 | // This exposes the temporal coupling by creating a bucket brigade.
36 | // Each function produces a result that the next function needs, so there is no reasonable way to call them out of order.
37 |
38 | //You might complain that this increases the complexity of the functions, and you’d be right.
39 | //But that extra syntactic complexity exposes the true temporal complexity of the situation.
40 |
41 | //Note that I left the instance variables in place. I presume that they are needed by private methods in the class.
42 | //Even so, I want the arguments in place to make the temporal coupling explicit.
43 |
--------------------------------------------------------------------------------
/Java/[G]eneral/[G07] Base Classes Depending on Their Derivatives.java:
--------------------------------------------------------------------------------
1 | public class Animal {
2 | public void feed(String foodType) {
3 | if (foodType.equals("meat")) {
4 | System.out.println("Animal is eating meat");
5 | } else if (foodType.equals("fish")) {
6 | System.out.println("Animal is eating fish");
7 | }
8 | }
9 | }
10 |
11 | public class Cat extends Animal {
12 | public void eat() {
13 | feed("fish");
14 | }
15 | }
16 |
17 | public class Dog extends Animal {
18 | public void eat() {
19 | feed("meat");
20 | }
21 | }
22 |
23 | // In this example, the "Animal" class contains a "Feed" method that accepts a food type and
24 | // prints out a message that the animal is eating.
25 | // The "Cat" and "Dog" classes then inherit from the "Animal" class and define their own "Eat" methods
26 | // that call the "Feed" method of the base class to feed the animal.
27 |
28 | // However, this violates the rule that base classes should not know about their derived classes.
29 | // To fix this, each derived class must define its own "Feed" method to implement the class-specific feeding logic.
30 |
31 | public abstract class Animal {
32 | public abstract void eat();
33 | }
34 |
35 | public class Cat extends Animal {
36 | @Override
37 | public void eat() {
38 | feed("fish");
39 | }
40 |
41 | private void feed(String foodType) {
42 | System.out.println("Cat is eating " + foodType);
43 | }
44 | }
45 |
46 | public class Dog extends Animal {
47 | @Override
48 | public void eat() {
49 | feed("meat");
50 | }
51 |
52 | private void feed(String foodType) {
53 | System.out.println("Dog is eating " + foodType);
54 | }
55 | }
--------------------------------------------------------------------------------
/Java/[J]ava and [C]sharp/[J02] Don’t Inherit Constants.java:
--------------------------------------------------------------------------------
1 | public class HourlyEmployee extends Employee {
2 | private int tenthsWorked;
3 | private double hourlyRate;
4 | public Money calculatePay() {
5 | int straightTime = Math.min(tenthsWorked, TENTHS_PER_WEEK);
6 | int overTime = tenthsWorked - straightTime;
7 | return new Money(
8 | hourlyRate * (tenthsWorked + OVERTIME_RATE * overTime)
9 | );
10 | }
11 | ...
12 | }
13 | // Where did the constants TENTHS_PER_WEEK and OVERTIME_RATE come from?
14 | // They might have come from class Employee;
15 | // so let's take a look at that:
16 |
17 | public abstract class Employee implements PayrollConstants {
18 | public abstract boolean isPayday();
19 | public abstract Money calculatePay();
20 | public abstract void deliverPay(Money pay);
21 | }
22 | // Nope, not there. But then where? Look closely at class Employee. It implements PayrollConstants.
23 |
24 | public interface PayrollConstants {
25 | public static final int TENTHS_PER_WEEK = 400;
26 | public static final double OVERTIME_RATE = 1.5;
27 | }
28 | // This is a hideous practice! The constants are hidden at the top of the inheritance hierarchy.
29 | // Ick! Don't use inheritance as a way to cheat the scoping rules of the language. Use a static import instead.
30 |
31 | import static PayrollConstants.*;
32 | public class HourlyEmployee extends Employee {
33 | private int tenthsWorked;
34 | private double hourlyRate;
35 | public Money calculatePay() {
36 | int straightTime = Math.min(tenthsWorked, TENTHS_PER_WEEK);
37 | int overTime = tenthsWorked - straightTime;
38 | return new Money(
39 | hourlyRate * (tenthsWorked + OVERTIME_RATE * overTime)
40 | );
41 | }
42 | ...
43 | }
--------------------------------------------------------------------------------
/Java/[G]eneral/[G14] Feature Envy.java:
--------------------------------------------------------------------------------
1 | public class HourlyPayCalculator {
2 | public Money calculateWeeklyPay(HourlyEmployee e) {
3 | int tenthRate = e.getTenthRate().getPennies();
4 | int tenthsWorked = e.getTenthsWorked();
5 | int straightTime = Math.min(400, tenthsWorked);
6 | int overTime = Math.max(0, tenthsWorked - straightTime);
7 | int straightPay = straightTime * tenthRate;
8 | int overtimePay = (int)Math.round(overTime*tenthRate*1.5);
9 | return new Money(straightPay + overtimePay);
10 | }
11 | }
12 | // The calculateWeeklyPay method reaches into the HourlyEmployee object to get the data on which it operates.
13 | // The calculateWeeklyPay method envies the scope of HourlyEmployee. It “wishes” that it could be inside HourlyEmployee.
14 |
15 | // All else being equal, we want to eliminate Feature Envy because it exposes the internals of one class to another.
16 | // Sometimes, however, Feature Envy is a necessary evil.Consider the following:
17 |
18 | public class HourlyEmployeeReport {
19 | private HourlyEmployee employee ;
20 | public HourlyEmployeeReport(HourlyEmployee e) {
21 | this.employee = e;
22 | }
23 | String reportHours() {
24 | return String.format(
25 | "Name: %s\tHours:%d.%1d\n",
26 | employee.getName(),
27 | employee.getTenthsWorked()/10,
28 | employee.getTenthsWorked()%10);
29 | }
30 | }
31 | // Clearly, the reportHours method envies the HourlyEmployee class.
32 | // On the other hand, we don’t want HourlyEmployee to have to know about the format of the report.
33 | // Moving that format string into the HourlyEmployee class would violate several principles of object oriented design.
34 | // It would couple HourlyEmployee to the format of the report, exposing it to changes in that format.
--------------------------------------------------------------------------------
/C#/[G]eneral/[G14] Feature Envy.cs:
--------------------------------------------------------------------------------
1 | public class HourlyPayCalculator
2 | {
3 | public Money CalculateWeeklyPay(HourlyEmployee e)
4 | {
5 | int tenthRate = e.GetTenthRate().GetPennies();
6 | int tenthsWorked = e.GetTenthsWorked();
7 | int straightTime = Math.Min(400, tenthsWorked);
8 | int overTime = Math.Max(0, tenthsWorked - straightTime);
9 | int straightPay = straightTime * tenthRate;
10 | int overtimePay = (int)Math.Round(overTime * tenthRate * 1.5);
11 | return new Money(straightPay + overtimePay);
12 | }
13 | }
14 |
15 | // The CalculateWeeklyPay method reaches into the HourlyEmployee object to get the data on which it operates.
16 | // The CalculateWeeklyPay method envies the scope of HourlyEmployee. It “wishes” that it could be inside HourlyEmployee.
17 |
18 | // All else being equal, we want to eliminate Feature Envy because it exposes the internals of one class to another.
19 | // Sometimes, however, Feature Envy is a necessary evil. Consider the following:
20 |
21 | public class HourlyEmployeeReport
22 | {
23 | private HourlyEmployee employee;
24 |
25 | public HourlyEmployeeReport(HourlyEmployee e)
26 | {
27 | this.employee = e;
28 | }
29 |
30 | public string ReportHours()
31 | {
32 | return string.Format(
33 | "Name: {0}\tHours: {1}.{2}\n",
34 | employee.GetName(),
35 | employee.GetTenthsWorked() / 10,
36 | employee.GetTenthsWorked() % 10);
37 | }
38 | }
39 | // Clearly, the ReportHours method envies the HourlyEmployee class.
40 | // On the other hand, we don’t want HourlyEmployee to have to know about the format of the report.
41 | // Moving that format string into the HourlyEmployee class would violate several principles of object oriented design.
42 | // It would couple HourlyEmployee to the format of the report, exposing it to changes in that format.
43 |
--------------------------------------------------------------------------------
/Java/[G]eneral/[G23] Prefer Polymorphism to IfElse or SwitchCase.java:
--------------------------------------------------------------------------------
1 | public void handleInput(String inputType, String inputValue) {
2 | switch (inputType) {
3 | case "button":
4 | handleButtonInput(inputValue);
5 | break;
6 | case "keyboard":
7 | handleKeyboardInput(inputValue);
8 | break;
9 | case "mouse":
10 | handleMouseInput(inputValue);
11 | break;
12 | default:
13 | System.out.println("Invalid input type.");
14 | break;
15 | }
16 | }
17 | // In this example, the HandleInput function takes an input device type and value
18 | // and uses a switch construct to determine which processing method to use based on the input device type.
19 | // This breaks the rule because the switch construct can be replaced by polymorphic objects,
20 | // such as creating an InputDevice base class and derived classes for each device type,
21 | // and using the HandleInput method within each class.
22 |
23 | // The code to be used using polymorphism might look like this:
24 | public abstract class InputDevice {
25 | public abstract void handleInput(String inputValue);
26 | }
27 |
28 | public class ButtonInputDevice extends InputDevice {
29 | @Override
30 | public void handleInput(String inputValue) {
31 | // Button Input Handling
32 | }
33 | }
34 |
35 | public class KeyboardInputDevice extends InputDevice {
36 | @Override
37 | public void handleInput(String inputValue) {
38 | // Keyboard Input Handling
39 | }
40 | }
41 |
42 | public class MouseInputDevice extends InputDevice {
43 | @Override
44 | public void handleInput(String inputValue) {
45 | // Mouse Input Handling
46 | }
47 | }
48 |
49 | public void handleInput(InputDevice inputDevice, String inputValue) {
50 | inputDevice.handleInput(inputValue);
51 | }
52 | // This avoids the switch statement and uses polymorphism instead.
--------------------------------------------------------------------------------
/C#/[G]eneral/[G23] Prefer Polymorphism to IfElse or SwitchCase.cs:
--------------------------------------------------------------------------------
1 | public void HandleInput(string inputType, string inputValue)
2 | {
3 | switch (inputType)
4 | {
5 | case "button":
6 | HandleButtonInput(inputValue);
7 | break;
8 | case "keyboard":
9 | HandleKeyboardInput(inputValue);
10 | break;
11 | case "mouse":
12 | HandleMouseInput(inputValue);
13 | break;
14 | default:
15 | Console.WriteLine("Invalid input type.");
16 | break;
17 | }
18 | }
19 | // In this example, the HandleInput function takes an input device type and value
20 | // and uses a switch construct to determine which processing method to use based on the input device type.
21 | // This breaks the rule because the switch construct can be replaced by polymorphic objects,
22 | // such as creating an InputDevice base class and derived classes for each device type,
23 | // and using the HandleInput method within each class.
24 |
25 | // The code to be used using polymorphism might look like this:
26 | public abstract class InputDevice
27 | {
28 | public abstract void HandleInput(string inputValue);
29 | }
30 |
31 | public class ButtonInputDevice : InputDevice
32 | {
33 | public override void HandleInput(string inputValue)
34 | {
35 | // Button Input Handling
36 | }
37 | }
38 |
39 | public class KeyboardInputDevice : InputDevice
40 | {
41 | public override void HandleInput(string inputValue)
42 | {
43 | // Keyboard Input Handling
44 | }
45 | }
46 |
47 | public class MouseInputDevice : InputDevice
48 | {
49 | public override void HandleInput(string inputValue)
50 | {
51 | // Mouse Input Handling
52 | }
53 | }
54 |
55 | public void HandleInput(InputDevice inputDevice, string inputValue)
56 | {
57 | inputDevice.HandleInput(inputValue);
58 | }
59 | // This avoids the switch statement and uses polymorphism instead.
--------------------------------------------------------------------------------
/Java/[N]ames/[N01] Choose Descriptive Names.java:
--------------------------------------------------------------------------------
1 | // Consider the code below. What does it do?
2 | // If I show you the code with well-chosen names, it will make perfect sense to you,
3 | // but like this it's just a hodge-podge of symbols and magic numbers.
4 |
5 | public int score() {
6 | int score = 0;
7 | int frame = 0;
8 | for (int frameNumber = 0; frameNumber < 10; frameNumber++) {
9 | if (isStrike(frame)) {
10 | score += 10 + nextTwoBallsForStrike(frame);
11 | frame += 1;
12 | } else if (isSpare(frame)) {
13 | score += 10 + nextBallForSpare(frame);
14 | frame += 2;
15 | } else {
16 | score += twoBallsInFrame(frame);
17 | frame += 2;
18 | }
19 | }
20 | return score;
21 | }
22 | // Here is the code the way it should be written.
23 | // This snippet is actually less complete than the one above.
24 | // Yet you can infer immediately what it is trying to do,
25 | // and you could very likely write the missing functions based on that inferred meaning.
26 | // The magic numbers are no longer magic, and the structure of the algorithm is compellingly descriptive.
27 |
28 | public int score() {
29 | int score = 0;
30 | int frame = 0;
31 | for (int frameNumber = 0; frameNumber < 10; frameNumber++) {
32 | if (isStrike(frame)) {
33 | score += 10 + nextTwoBallsForStrike(frame);
34 | frame += 1;
35 | } else if (isSpare(frame)) {
36 | score += 10 + nextBallForSpare(frame);
37 | frame += 2;
38 | } else {
39 | score += twoBallsInFrame(frame);
40 | frame += 2;
41 | }
42 | }
43 | return score;
44 | }
45 | // You can infer the implementation of isStrike() by looking at the code above.
46 | // When you read the isStrike method, it will be "pretty much what you expected".
47 |
48 | private boolean isStrike(int frame) {
49 | return rolls[frame] == 10;
50 | }
--------------------------------------------------------------------------------
/C#/[N]ames/[N01] Choose Descriptive Names.cs:
--------------------------------------------------------------------------------
1 | // Consider the code below. What does it do?
2 | // If I show you the code with well-chosen names, it will make perfect sense to you,
3 | // but like this it's just a hodge-podge of symbols and magic numbers.
4 |
5 | public int Score()
6 | {
7 | int score = 0;
8 | int frame = 0;
9 |
10 | for (int frameNumber = 0; frameNumber < 10; frameNumber++)
11 | {
12 | if (IsStrike(frame))
13 | {
14 | score += 10 + NextTwoBallsForStrike(frame);
15 | frame += 1;
16 | }
17 | else if (IsSpare(frame))
18 | {
19 | score += 10 + NextBallForSpare(frame);
20 | frame += 2;
21 | }
22 | else
23 | {
24 | score += TwoBallsInFrame(frame);
25 | frame += 2;
26 | }
27 | }
28 | return score;
29 | }
30 | // Here is the code the way it should be written.
31 | // This snippet is actually less complete than the one above.
32 | // Yet you can infer immediately what it is trying to do,
33 | // and you could very likely write the missing functions based on that inferred meaning.
34 | // The magic numbers are no longer magic, and the structure of the algorithm is compellingly descriptive.
35 |
36 | public int Score()
37 | {
38 | int score = 0;
39 | int frame = 0;
40 |
41 | for (int frameNumber = 0; frameNumber < 10; frameNumber++)
42 | {
43 | if (IsStrike(frame))
44 | {
45 | score += 10 + NextTwoBallsForStrike(frame);
46 | frame += 1;
47 | }
48 | else if (IsSpare(frame))
49 | {
50 | score += 10 + NextBallForSpare(frame);
51 | frame += 2;
52 | }
53 | else
54 | {
55 | score += TwoBallsInFrame(frame);
56 | frame += 2;
57 | }
58 | }
59 | return score;
60 | }
61 | // You can infer the implementation of IsStrike() by looking at the code above.
62 | // When you read the IsStrike method, it will be "pretty much what you expected".
63 |
64 | private bool IsStrike(int frame)
65 | {
66 | return _rolls[frame] == 10;
67 | }
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Heuristics for "Clean Code"
2 |
3 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
4 |
5 | The following is a set of guidelines for contributing to Heuristics for "Clean Code". These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
6 |
7 | #### Table Of Contents
8 |
9 | [I don't want to read this whole thing, I just have a question!!!](#i-dont-want-to-read-this-whole-thing-i-just-have-a-question)\
10 | [Contact with me](#contact-with-me)\
11 | [Found a mistake?](#found-a-mistake?)\
12 | [Ready to contribute?](#ready-to-contribute?)\
13 | [License](#license)
14 |
15 | ## I don't want to read this whole thing I just have a question!!!
16 |
17 | You can:
18 | - check or create [discussions](../../discussions)
19 | - check or create [issues](../../issues)
20 |
21 | or just
22 | ## Contact with me
23 | - [linkedin](https://www.linkedin.com/in/serhii-chepets/)
24 | - [telegram](https://t.me/SaintZet)
25 | - serhii.chepets@gmail.com
26 |
27 | ## Found a mistake?
28 |
29 | You can use [issues](../../issues) to report mistakes. Please also feel free to submit a Pull Request with a fix for the mistakes!
30 |
31 | ## Ready to contribute?
32 |
33 | ### How to submit Pull Requests
34 |
35 | 1. Fork this repository
36 | 2. Clone your fork and create a **new branch**: \
37 | `$ git checkout https://github.com/SaintZet/HeuristicsForCleanCode -b examples/add-new-example`.
38 | 4. Make changes and test
39 | 5. Publish the changes to your fork
40 | 6. Submit a Pull Request with comprehensive description of changes
41 | 7. Pull Request must target `master` branch
42 | 8. For a Pull Request to be merged:
43 | * A project member must review and approve it
44 |
45 | ### Important to remember
46 |
47 | Not only the code should be clean. Сreate branches with **meaningful** names, describe commits, create an issue to describe your contribution.
48 |
49 | ## License
50 |
51 | Don't worry, the project is not licensed and is in the public domain. But I ask you to treat my work with respect and notify me of your initiatives.
52 |
--------------------------------------------------------------------------------
/Java/[G]eneral/[G22] Make Logical Dependencies Physical.java:
--------------------------------------------------------------------------------
1 | // Imagine that you are writing a function that prints a plain text report of hours worked by employees.
2 | // One class named HourlyReporter gathers all the data into a convenient form and then passes it to HourlyReportFormatter to print it.
3 | public class HourlyReporter {
4 | private HourlyReportFormatter formatter;
5 | private List page;
6 | private final int PAGE_SIZE = 55;
7 | public HourlyReporter(HourlyReportFormatter formatter) {
8 | this.formatter = formatter;
9 | page = new ArrayList();
10 | }
11 | public void generateReport(List employees) {
12 | for (HourlyEmployee e : employees) {
13 | addLineItemToPage(e);
14 | if (page.size() == PAGE_SIZE)
15 | printAndClearItemList();
16 | }
17 | if (page.size() > 0)
18 | printAndClearItemList();
19 | }
20 | private void printAndClearItemList() {
21 | formatter.format(page);
22 | page.clear();
23 | }
24 | private void addLineItemToPage(HourlyEmployee e) {
25 | LineItem item = new LineItem();
26 | item.name = e.getName();
27 | item.hours = e.getTenthsWorked() / 10;
28 | item.tenths = e.getTenthsWorked() % 10;
29 | page.add(item);
30 | }
31 | public class LineItem {
32 | public String name;
33 | public int hours;
34 | public int tenths;
35 | }
36 | }
37 |
38 | // This code has a logical dependency that has not been physicalized.
39 | // It is the constant PAGE_SIZE. Why should the HourlyReporter know the size of the page?
40 | // Page size should be the responsibility of the HourlyReportFormatter.
41 |
42 | // The fact that PAGE_SIZE is declared in HourlyReporter represents a misplaced responsibility [G17]
43 | // that causes HourlyReporter to assume that it knows what the page size ought to be.
44 | // Such an assumption is a logical dependency. HourlyReporter depends on the fact that HourlyReportFormatter can deal with page sizes of 55.
45 | // If some implementation of HourlyReportFormatter could not deal with such sizes, then there would be an error.
46 |
47 | // We can physicalize this dependency by creating a new method in HourlyReportFormatter named getMaxPageSize().
48 | // HourlyReporter will then call that function rather than using the PAGE_SIZE constant.
--------------------------------------------------------------------------------
/C#/[G]eneral/[G22] Make Logical Dependencies Physical.cs:
--------------------------------------------------------------------------------
1 | // Imagine that you are writing a function that prints a plain text report of hours worked by employees.
2 | // One class named HourlyReporter gathers all the data into a convenient form and then passes it to HourlyReportFormatter to print it.
3 | public class HourlyReporter
4 | {
5 | private const int PAGE_SIZE = 55;
6 | private HourlyReportFormatter _formatter;
7 | private List _page;
8 |
9 | public HourlyReporter(HourlyReportFormatter formatter)
10 | {
11 | _formatter = formatter;
12 | _page = new List();
13 | }
14 |
15 | public void generateReport(List employees)
16 | {
17 | foreach (HourlyEmployee e in employees)
18 | {
19 | AddLineItemToPage(e);
20 |
21 | if (_page.Count() == PAGE_SIZE)
22 | PrintAndClearItemList();
23 | }
24 |
25 | if (_page.Count() > 0)
26 | printAndClearItemList();
27 | }
28 |
29 | private void PrintAndClearItemList()
30 | {
31 | _formatter.Format(_page);
32 | _page.Clear();
33 | }
34 |
35 | private void AddLineItemToPage(HourlyEmployee e)
36 | {
37 | LineItem item = new LineItem();
38 | item.Name = e.GetName();
39 | item.Hours = e.GetTenthsWorked() / 10;
40 | item.Tenths = e.GetTenthsWorked() % 10;
41 | _page.Add(item);
42 | }
43 | public class LineItem
44 | {
45 | public string Name;
46 | public int Hours;
47 | public int Tenths;
48 | }
49 | }
50 |
51 | // This code has a logical dependency that has not been physicalized.
52 | // It is the constant PAGE_SIZE. Why should the HourlyReporter know the size of the page?
53 | // Page size should be the responsibility of the HourlyReportFormatter.
54 |
55 | // The fact that PAGE_SIZE is declared in HourlyReporter represents a misplaced responsibility [G17]
56 | // that causes HourlyReporter to assume that it knows what the page size ought to be.
57 | // Such an assumption is a logical dependency. HourlyReporter depends on the fact that HourlyReportFormatter can deal with page sizes of 55.
58 | // If some implementation of HourlyReportFormatter could not deal with such sizes, then there would be an error.
59 |
60 | // We can physicalize this dependency by creating a new method in HourlyReportFormatter named GetMaxPageSize().
61 | // HourlyReporter will then call that function rather than using the PAGE_SIZE constant.
--------------------------------------------------------------------------------
/C#/[G]eneral/[G34] Functions Should Descend Only One Level of Abstraction.cs:
--------------------------------------------------------------------------------
1 | public string Render()
2 | {
3 | StringBuffer html = new StringBuffer("
0)
5 | html.Append(" size=\"").Append(size + 1).Append("\"");
6 | html.append(">");
7 | return html.ToString();
8 | }
9 | // A moment’s study and you can see what’s going on.
10 | // This function constructs the HTML tag that draws a horizontal rule across the page.
11 | // The height of that rule is specified in the size variable.
12 |
13 | // Now look again. This method is mixing at least two levels of abstraction.
14 | // The first is the notion that a horizontal rule has a size. The second is the syntax of the HR tag itself.
15 | // This code comes from the HruleWidget module in FitNesse.
16 | // This module detects a row of four or more dashes and converts it into the appropriate HR tag.
17 | // The more dashes, the larger the size.
18 |
19 | // I refactored this bit of code as follows. Note that I changed the name of the size field to reflect its true purpose.
20 | // It held the number of extra dashes.
21 |
22 | public string Render()
23 | {
24 | HtmlTag hr = new HtmlTag("hr");
25 |
26 | if (extraDashes > 0)
27 | hr.AddAttribute("size", HrSize(extraDashes));
28 |
29 | return hr.html();
30 | }
31 |
32 | private string HrSize(int height)
33 | {
34 | int hrSize = height + 1;
35 |
36 | return string.Format("%d", hrSize);
37 | }
38 | // This change separates the two levels of abstraction nicely.
39 | // The render function simply constructs an HR tag, without having to know anything about the HTML syntax of that tag.
40 | // The HtmlTag module takes care of all the nasty syntax issues.
41 |
42 | // Indeed, by making this change I caught a subtle error.
43 | // The original code did not put the closing slash on the HR tag, as the XHTML standard would have it.
44 | // The HtmlTag module had been changed to conform to XHTML long ago.
45 |
46 | // Separating levels of abstraction is one of the most important functions of refactoring, and it’s one of the hardest to do well.
47 | // As an example, look at the code below. This was my first attempt at separating the abstraction levels in the HruleWidget.render method.
48 |
49 | public string Render()
50 | {
51 | HtmlTag hr = new HtmlTag("hr");
52 |
53 | if (size > 0)
54 | hr.AddAttribute("size", ""+(size+1));
55 |
56 | return hr.html();
57 | }
58 | // My goal, at this point, was to create the necessary separation and get the tests to pass
59 | // I accomplished that goal easily, but the result was a function that still had mixed levels of abstraction.
60 | // In this case the mixed levels were the construction of the HR tag and the interpretation and formatting of the size variable.
61 | // This points out that when you break a function along lines of abstraction, you often uncover new lines of abstraction
62 | // that were obscured by the previous structure.
--------------------------------------------------------------------------------
/Java/[G]eneral/[G34] Functions Should Descend Only One Level of Abstraction.java:
--------------------------------------------------------------------------------
1 | public String render() throws Exception
2 | {
3 | StringBuffer html = new StringBuffer("
0)
5 | html.append(" size=\"").append(size + 1).append("\"");
6 | html.append(">");
7 | return html.toString();
8 | }
9 | // A moment’s study and you can see what’s going on.
10 | // This function constructs the HTML tag that draws a horizontal rule across the page.
11 | // The height of that rule is specified in the size variable.
12 |
13 | // Now look again. This method is mixing at least two levels of abstraction.
14 | // The first is the notion that a horizontal rule has a size. The second is the syntax of the HR tag itself.
15 | // This code comes from the HruleWidget module in FitNesse.
16 | // This module detects a row of four or more dashes and converts it into the appropriate HR tag.
17 | // The more dashes, the larger the size.
18 |
19 | // I refactored this bit of code as follows. Note that I changed the name of the size field to reflect its true purpose.
20 | // It held the number of extra dashes.
21 |
22 | public String render() throws Exception
23 | {
24 | HtmlTag hr = new HtmlTag("hr");
25 | if (extraDashes > 0)
26 | hr.addAttribute("size", hrSize(extraDashes));
27 | return hr.html();
28 | }
29 | private String hrSize(int height)
30 | {
31 | int hrSize = height + 1;
32 | return String.format("%d", hrSize);
33 | }
34 | // This change separates the two levels of abstraction nicely.
35 | // The render function simply constructs an HR tag, without having to know anything about the HTML syntax of that tag.
36 | // The HtmlTag module takes care of all the nasty syntax issues.
37 |
38 | // Indeed, by making this change I caught a subtle error.
39 | // The original code did not put the closing slash on the HR tag, as the XHTML standard would have it.
40 | // The HtmlTag module had been changed to conform to XHTML long ago.
41 |
42 | // Separating levels of abstraction is one of the most important functions of refactoring, and it’s one of the hardest to do well.
43 | // As an example, look at the code below. This was my first attempt at separating the abstraction levels in the HruleWidget.render method.
44 |
45 | public String render() throws Exception
46 | {
47 | HtmlTag hr = new HtmlTag("hr");
48 | if (size > 0) {
49 | hr.addAttribute("size", ""+(size+1));
50 | }
51 | return hr.html();
52 | }
53 | // My goal, at this point, was to create the necessary separation and get the tests to pass
54 | // I accomplished that goal easily, but the result was a function that still had mixed levels of abstraction.
55 | // In this case the mixed levels were the construction of the HR tag and the interpretation and formatting of the size variable.
56 | // This points out that when you break a function along lines of abstraction, you often uncover new lines of abstraction
57 | // that were obscured by the previous structure.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ## :exclamation: *All heuristic rules in [GitHub Wiki](https://github.com/SaintZet/HeuristicsForClearCode/wiki)* :exclamation:
6 |
7 | ### :bulb: Usage
8 |
9 | The purpose of this knowledge library is to **structure** and **quickly access** various "[code smell](https://en.wikipedia.org/wiki/Code_smell)" and methods of dealing with them.
10 |
11 | Idea of creating this repository came to me while reading Robert Martin's "*Clean Code*". I realized that I would like to have all these rules at my fingertips in a convenient format.
12 | I've shortened the information a bit so that it can be referred to as a hint and referenced without using a different context.
13 |
14 | One of the advantages of this project is the ability to copy links to specific rules or code smells.
15 | You can use thise links in private correspondence with colleagues, in comments on pull requests, in forums, or anywhere else.
16 | For example in your amazing readme file without [too much information](https://github.com/SaintZet/HeuristicsForCleanCode/wiki/%5BG%5Deneral#g08-too-much-information) :smile:
17 |
18 | Most of the rules are illustrated with code examples. You can view the code without rules, each file is signed with the name of the rule and contain comments.
19 | To the extent possible, these rules will be further supported by code examples. And perhaps personal observations.
20 |
21 | I hope this project will be useful for you and I invite you to participate in [discussions](../../discussions), create [issues](../../issues) and contribute your examples.
22 |
23 | ### :information_source: Acknowledgment
24 |
25 | The inspiration for writing these heuristics in a wiki format was Robert C. Martin "*Clean Code*". In turn, Robert Martin refers to other works:
26 | - Refactoring: Improving the Design of Existing Code, Martin Fowler et al., Addison-Wesley, 1999.
27 | - The Pragmatic Programmer, Andrew Hunt, Dave Thomas, Addison-Wesley, 2000.
28 | - Design Patterns: Elements of Reusable Object-Oriented Software, Gamma et al., Addison-Wesley, 1996.
29 | - Smalltalk Best Practice Patterns, Kent Beck, Prentice Hall, 1997.
30 | - Implementation Patterns, Kent Beck, Addison-Wesley, 2008.
31 | - Agile Software Development: Principles, Patterns, and Practices, Robert C. Martin, Prentice Hall, 2002.
32 | - Domain Driven Design, Eric Evans, Addison-Wesley, 2003.
33 |
34 | I am immensely grateful to these people for their work.
35 |
36 | ### :warning: Important
37 |
38 | Clean code is not written by following a set of rules. You don’t become a software craftsman by learning a list of heuristics. Professionalism and craftsmanship come from values that drive disciplines.
39 |
40 |
41 |
42 | Перевод 
43 |
44 |
45 | ### :exclamation: *All heuristic rules on [GitHub Wiki](https://github.com/SaintZet/HeuristicsForClearCode/wiki)* :exclamation:
46 |
47 | ### :bulb: Применение
48 |
49 | Целью данной библиотеки знаний является **структурированный** и **быстрый доступ** к различным "[запахам кода](https://ru.wikipedia.org/wiki/Код_с_запашком)" и методов борьбы с ними.
50 |
51 | Идея создания этого репозитория пришла мне, когда я читал "*Чистый код*" Роберта Мартина. Я понял, что хотел бы иметь все эти правила под рукой в удобном формате.
52 | Я немного ужал информацию для того, что бы к ней можно было обратиться как к подсказке и ссылаться на нее не используя другой контекст.
53 |
54 | Одним из преимуществ этого проекта является возможность копирования ссылок на определенные правила или запахи кода.
55 | Вы можете использовать эти ссылки в личной переписке с коллегами, в комментариях к пулл-реквестам, на форумах или где-либо еще.
56 | Например, в вашем прекрасном файле readme без [лишней информации](https://github.com/SaintZet/HeuristicsForCleanCode/wiki/%5BG%5Deneral#g08-too-much-information) :smile:
57 |
58 | Большая часть правил проиллюстрированы примерами кода. Вы можете просматривать код и без правил, каждый файл подписан именем правила и содержит комментарии.
59 | По мере возможности, данные правила будут еще больше подкрепляться примерами кода. И возможно, личными наблюдениями.
60 |
61 | Я надеюсь вам этот проект пригодиться и приглашаю участвовать в [обсуждениях](../../discussions), создании [задач](../../issues) и контребьютить свои примеры.
62 |
63 | ### :information_source: Благодарность
64 |
65 | Вдохновением для написания этих эвристических правил в виде вики послужила книга Роберта Мартина "*Чистый код*". В свою очередь Роберт Мартин ссылается на другие не менее значимые труды:
66 | - Refactoring: Improving the Design of Existing Code, Martin Fowler et al., Addison-Wesley, 1999.
67 | - The Pragmatic Programmer, Andrew Hunt, Dave Thomas, Addison-Wesley, 2000.
68 | - Design Patterns: Elements of Reusable Object Oriented Software, Gamma et al., Addison-Wesley, 1996.
69 | - Smalltalk Best Practice Patterns, Kent Beck, Prentice Hall, 1997.
70 | - Implementation Patterns, Kent Beck, Addison-Wesley, 2008.
71 | - Agile Software Development: Principles, Patterns, and Practices, Robert C. Martin, Prentice Hall, 2002.
72 | - Domain Driven Design, Eric Evans, Addison-Wesley, 2003.
73 |
74 | Я безмерно благодарен этим людям за их работу.
75 |
76 | ### :warning: Важно
77 |
78 | Невозможно написать чистый код, действуя по списку правил. Нельзя стать мастером, изучив набор эвристик. Профессионализм и мастерство формируются на основе ценностей, которыми вы руководствуетесь в обучении.
79 |
80 |
81 |
82 |
83 | Переклад 
84 |
85 |
86 | ### :exclamation: *Всі евристичні правила в [GitHub Wiki](https://github.com/SaintZet/HeuristicsForClearCode/wiki)* :exclamation:
87 |
88 | ### :bulb: Застосування
89 |
90 | Метою даної бібліотеки знань є **структурований** та **швидкий доступ** до різних "[запахів коду](https://uk.wikipedia.org/wiki/Запахи_коду)" та методів боротьби з ними.
91 |
92 | Ідея створення цього репозиторію прийшла до мене, коли я читав "*Чистий код*" Роберта Мартина. Я зрозумів, що хотів би мати всі ці правила під рукою в зручному форматі.
93 | Я трохи зтиснув інформацію для того, щоб до неї можна було звернутися як до підказки та посилатися на неї не використовуючи інший контекст.
94 |
95 | Однією з переваг цього проекту є можливість копіювати посилання на конкретні правила або запахи коду.
96 | Ви можете використовувати ці посилання у приватному листуванні з колегами, в коментарях до запитів на отримання, на форумах або де завгодно.
97 | Наприклад, у вашому дивовижному файлі readme без [зайвої інформації](https://github.com/SaintZet/HeuristicsForCleanCode/wiki/%5BG%5Deneral#g08-too-much-information)
98 |
99 | Більшість правил проілюстровані прикладами коду. Ви можете переглядати код без правил, кожен файл підписаний ім'ям правила і містить коментарі.
100 | У міру можливості дані правила будуть ще більше підкріплюватися прикладами коду. І можливо, особистими спостереженнями.
101 |
102 | Я сподіваюсь, що цей проект стане вам у нагоді. Також запрошую брати участь в [обговореннях](../../discussions), створенні [задач](../../issues) та контриб‘ютити свої приклади.
103 |
104 | ### :information_source: Подяка
105 |
106 | Натхненням для написання цих евристичних правил у вигляді вікі послужила книга Роберта Мартіна "*Чистий код*". У свою чергу Роберт Мартін посилається на інші не менш важливі праці:
107 | - Refactoring: Improving the Design of Existing Code, Martin Fowler et al., Addison-Wesley, 1999.
108 | - The Pragmatic Programmer, Andrew Hunt, Dave Thomas, Addison-Wesley, 2000.
109 | - Design Patterns: Elements of Reusable Object Oriented Software, Gamma et al., Addison-Wesley, 1996.
110 | - Smalltalk Best Practice Patterns, Kent Beck, Prentice Hall, 1997.
111 | - Implementation Patterns, Kent Beck, Addison-Wesley, 2008.
112 | - Agile Software Development: Principles, Patterns, and Practices, Robert C. Martin, Prentice Hall, 2002.
113 | - Domain Driven Design, Eric Evans, Addison-Wesley, 2003.
114 |
115 | Я безмежно вдячний цим людям за їх працю.
116 |
117 | ### :warning: Важливо
118 |
119 | Неможливо написати чистий код, діючи за переліком правил. Не можна стати майстром, вивчивши набір евристик. Професіоналізм та майстерність формуються на основі цінностей, якими ви керуєтеся у навчанні.
120 |
121 |
122 |
--------------------------------------------------------------------------------