├── 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 | --------------------------------------------------------------------------------