From fb96172b55b8e65bc7b7cea0c177afcf3271d390 Mon Sep 17 00:00:00 2001 From: jhauga Date: Mon, 3 Nov 2025 20:59:27 -0500 Subject: [PATCH] better account for incompatible code block indents --- .../object-calisthenics.instructions.md | 305 +++++++++--------- scripts/indent-nested-md-code.js | 12 +- 2 files changed, 165 insertions(+), 152 deletions(-) diff --git a/instructions/object-calisthenics.instructions.md b/instructions/object-calisthenics.instructions.md index ea76205..d3a9a99 100644 --- a/instructions/object-calisthenics.instructions.md +++ b/instructions/object-calisthenics.instructions.md @@ -8,12 +8,14 @@ description: Enforces Object Calisthenics principles for business domain code to > Examples may be added later if needed. ## Objective + This rule enforces the principles of Object Calisthenics to ensure clean, maintainable, and robust code in the backend, **primarily for business domain code**. ## Scope and Application + - **Primary focus**: Business domain classes (aggregates, entities, value objects, domain services) - **Secondary focus**: Application layer services and use case handlers -- **Exemptions**: +- **Exemptions**: - DTOs (Data Transfer Objects) - API models/contracts - Configuration classes @@ -22,7 +24,6 @@ This rule enforces the principles of Object Calisthenics to ensure clean, mainta ## Key Principles - 1. **One Level of Indentation per Method**: - Ensure methods are simple and do not exceed one level of indentation. @@ -57,6 +58,7 @@ This rule enforces the principles of Object Calisthenics to ensure clean, mainta } } ``` + 2. **Don't Use the ELSE Keyword**: - Avoid using the `else` keyword to reduce complexity and improve readability. @@ -81,6 +83,7 @@ This rule enforces the principles of Object Calisthenics to ensure clean, mainta ``` Sample Fail fast principle: + ```csharp public void ProcessOrder(Order order) { if (order == null) throw new ArgumentNullException(nameof(order)); @@ -115,7 +118,7 @@ This rule enforces the principles of Object Calisthenics to ensure clean, mainta this.value = value; } } - ``` + ``` 4. **First Class Collections**: - Use collections to encapsulate data and behavior, rather than exposing raw data structures. @@ -148,156 +151,156 @@ First Class Collections: a class that contains an array as an attribute should n .Count(); } } - ``` - - 5. **One Dot per Line**: - - Limit the number of method calls in a single line to improve readability and maintainability. - - ```csharp - // Bad Example - Multiple dots in a single line - public void ProcessOrder(Order order) { - var userEmail = order.User.GetEmail().ToUpper().Trim(); - // Do something with userEmail - } - // Good Example - One dot per line - public void ProcessOrder(Order order) { - var user = order.User; - var email = user.GetEmail(); - var userEmail = email.ToUpper().Trim(); - // Do something with userEmail - } - ``` - - 6. **Don't abbreviate**: - - Use meaningful names for classes, methods, and variables. - - Avoid abbreviations that can lead to confusion. - - ```csharp - // Bad Example - Abbreviated names - public class U { - public string N { get; set; } - } - // Good Example - Meaningful names - public class User { - public string Name { get; set; } - } - ``` - - 7. **Keep entities small (Class, method, namespace or package)**: - - Limit the size of classes and methods to improve code readability and maintainability. - - Each class should have a single responsibility and be as small as possible. - - Constraints: - - Maximum 10 methods per class - - Maximum 50 lines per class - - Maximum 10 classes per package or namespace - - ```csharp - // Bad Example - Large class with multiple responsibilities - public class UserManager { - public void CreateUser(string name) { /*...*/ } - public void DeleteUser(int id) { /*...*/ } - public void SendEmail(string email) { /*...*/ } - } + ``` - // Good Example - Small classes with single responsibility - public class UserCreator { - public void CreateUser(string name) { /*...*/ } - } - public class UserDeleter { - public void DeleteUser(int id) { /*...*/ } - } +5. **One Dot per Line**: + - Limit the number of method calls in a single line to improve readability and maintainability. - public class UserUpdater { - public void UpdateUser(int id, string name) { /*...*/ } - } - ``` - - - 8. **No Classes with More Than Two Instance Variables**: - - Encourage classes to have a single responsibility by limiting the number of instance variables. - - Limit the number of instance variables to two to maintain simplicity. - - Do not count ILogger or any other logger as instance variable. - - ```csharp - // Bad Example - Class with multiple instance variables - public class UserCreateCommandHandler { - // Bad: Too many instance variables - private readonly IUserRepository userRepository; - private readonly IEmailService emailService; - private readonly ILogger logger; - private readonly ISmsService smsService; + ```csharp + // Bad Example - Multiple dots in a single line + public void ProcessOrder(Order order) { + var userEmail = order.User.GetEmail().ToUpper().Trim(); + // Do something with userEmail + } + // Good Example - One dot per line + public void ProcessOrder(Order order) { + var user = order.User; + var email = user.GetEmail(); + var userEmail = email.ToUpper().Trim(); + // Do something with userEmail + } + ``` - public UserCreateCommandHandler(IUserRepository userRepository, IEmailService emailService, ILogger logger, ISmsService smsService) { - this.userRepository = userRepository; - this.emailService = emailService; - this.logger = logger; - this.smsService = smsService; - } - } +6. **Don't abbreviate**: + - Use meaningful names for classes, methods, and variables. + - Avoid abbreviations that can lead to confusion. - // Good: Class with two instance variables - public class UserCreateCommandHandler { - private readonly IUserRepository userRepository; - private readonly INotificationService notificationService; - private readonly ILogger logger; // This is not counted as instance variable + ```csharp + // Bad Example - Abbreviated names + public class U { + public string N { get; set; } + } + // Good Example - Meaningful names + public class User { + public string Name { get; set; } + } + ``` - public UserCreateCommandHandler(IUserRepository userRepository, INotificationService notificationService, ILogger logger) { - this.userRepository = userRepository; - this.notificationService = notificationService; - this.logger = logger; - } - } - ``` - - 9. **No Getters/Setters in Domain Classes**: - - Avoid exposing setters for properties in domain classes. - - Use private constructors and static factory methods for object creation. - - **Note**: This rule applies primarily to domain classes, not DTOs or data transfer objects. - - ```csharp - // Bad Example - Domain class with public setters - public class User { // Domain class - public string Name { get; set; } // Avoid this in domain classes - } - - // Good Example - Domain class with encapsulation - public class User { // Domain class - private string name; - private User(string name) { this.name = name; } - public static User Create(string name) => new User(name); - } - - // Acceptable Example - DTO with public setters - public class UserDto { // DTO - exemption applies - public string Name { get; set; } // Acceptable for DTOs - } - ``` - - ## Implementation Guidelines - - **Domain Classes**: - - Use private constructors and static factory methods for creating instances. - - Avoid exposing setters for properties. - - Apply all 9 rules strictly for business domain code. - - - **Application Layer**: - - Apply these rules to use case handlers and application services. - - Focus on maintaining single responsibility and clean abstractions. - - - **DTOs and Data Objects**: - - Rules 3 (wrapping primitives), 8 (two instance variables), and 9 (no getters/setters) may be relaxed for DTOs. - - Public properties with getters/setters are acceptable for data transfer objects. - - - **Testing**: - - Ensure tests validate the behavior of objects rather than their state. - - Test classes may have relaxed rules for readability and maintainability. - - - **Code Reviews**: - - Enforce these rules during code reviews for domain and application code. - - Be pragmatic about infrastructure and DTO code. - - ## References - - [Object Calisthenics - Original 9 Rules by Jeff Bay](https://www.cs.helsinki.fi/u/luontola/tdd-2009/ext/ObjectCalisthenics.pdf) - - [ThoughtWorks - Object Calisthenics](https://www.thoughtworks.com/insights/blog/object-calisthenics) - - [Clean Code: A Handbook of Agile Software Craftsmanship - Robert C. Martin](https://www.oreilly.com/library/view/clean-code-a/9780136083238/) - \ No newline at end of file +7. **Keep entities small (Class, method, namespace or package)**: + - Limit the size of classes and methods to improve code readability and maintainability. + - Each class should have a single responsibility and be as small as possible. + + Constraints: + - Maximum 10 methods per class + - Maximum 50 lines per class + - Maximum 10 classes per package or namespace + + ```csharp + // Bad Example - Large class with multiple responsibilities + public class UserManager { + public void CreateUser(string name) { /*...*/ } + public void DeleteUser(int id) { /*...*/ } + public void SendEmail(string email) { /*...*/ } + } + + // Good Example - Small classes with single responsibility + public class UserCreator { + public void CreateUser(string name) { /*...*/ } + } + public class UserDeleter { + public void DeleteUser(int id) { /*...*/ } + } + + public class UserUpdater { + public void UpdateUser(int id, string name) { /*...*/ } + } + ``` + +8. **No Classes with More Than Two Instance Variables**: + - Encourage classes to have a single responsibility by limiting the number of instance variables. + - Limit the number of instance variables to two to maintain simplicity. + - Do not count ILogger or any other logger as instance variable. + + ```csharp + // Bad Example - Class with multiple instance variables + public class UserCreateCommandHandler { + // Bad: Too many instance variables + private readonly IUserRepository userRepository; + private readonly IEmailService emailService; + private readonly ILogger logger; + private readonly ISmsService smsService; + + public UserCreateCommandHandler(IUserRepository userRepository, IEmailService emailService, ILogger logger, ISmsService smsService) { + this.userRepository = userRepository; + this.emailService = emailService; + this.logger = logger; + this.smsService = smsService; + } + } + + // Good: Class with two instance variables + public class UserCreateCommandHandler { + private readonly IUserRepository userRepository; + private readonly INotificationService notificationService; + private readonly ILogger logger; // This is not counted as instance variable + + public UserCreateCommandHandler(IUserRepository userRepository, INotificationService notificationService, ILogger logger) { + this.userRepository = userRepository; + this.notificationService = notificationService; + this.logger = logger; + } + } + ``` + +9. **No Getters/Setters in Domain Classes**: + - Avoid exposing setters for properties in domain classes. + - Use private constructors and static factory methods for object creation. + - **Note**: This rule applies primarily to domain classes, not DTOs or data transfer objects. + + ```csharp + // Bad Example - Domain class with public setters + public class User { // Domain class + public string Name { get; set; } // Avoid this in domain classes + } + + // Good Example - Domain class with encapsulation + public class User { // Domain class + private string name; + private User(string name) { this.name = name; } + public static User Create(string name) => new User(name); + } + + // Acceptable Example - DTO with public setters + public class UserDto { // DTO - exemption applies + public string Name { get; set; } // Acceptable for DTOs + } + ``` + +## Implementation Guidelines + +- **Domain Classes**: + - Use private constructors and static factory methods for creating instances. + - Avoid exposing setters for properties. + - Apply all 9 rules strictly for business domain code. + +- **Application Layer**: + - Apply these rules to use case handlers and application services. + - Focus on maintaining single responsibility and clean abstractions. + +- **DTOs and Data Objects**: + - Rules 3 (wrapping primitives), 8 (two instance variables), and 9 (no getters/setters) may be relaxed for DTOs. + - Public properties with getters/setters are acceptable for data transfer objects. + +- **Testing**: + - Ensure tests validate the behavior of objects rather than their state. + - Test classes may have relaxed rules for readability and maintainability. + +- **Code Reviews**: + - Enforce these rules during code reviews for domain and application code. + - Be pragmatic about infrastructure and DTO code. + +## References + +- [Object Calisthenics - Original 9 Rules by Jeff Bay](https://www.cs.helsinki.fi/u/luontola/tdd-2009/ext/ObjectCalisthenics.pdf) +- [ThoughtWorks - Object Calisthenics](https://www.thoughtworks.com/insights/blog/object-calisthenics) +- [Clean Code: A Handbook of Agile Software Craftsmanship - Robert C. Martin](https://www.oreilly.com/library/view/clean-code-a/9780136083238/) diff --git a/scripts/indent-nested-md-code.js b/scripts/indent-nested-md-code.js index 8d8ed2f..899c558 100644 --- a/scripts/indent-nested-md-code.js +++ b/scripts/indent-nested-md-code.js @@ -104,7 +104,17 @@ function processFile(filePath) { continue; } - // Otherwise, treat as nested inner fence start; indent til matching inner fence (inclusive) + // Potential nested fence - check if it has language info (opening fence) + // If rest is empty or just whitespace, it's likely a sloppy closing fence, not a nested opening + const hasLangInfo = restTrim.length > 0; + + if (!hasLangInfo) { + // Sloppy closing fence without nested content - don't indent, just skip + i++; + continue; + } + + // This is an actual nested opening fence; indent til matching inner fence (inclusive) changed = true; const innerTicksLen = ticksLen; lines[i] = ' ' + lines[i];