better account for incompatible code block indents

This commit is contained in:
jhauga 2025-11-03 20:59:27 -05:00
parent 73c626d041
commit fb96172b55
2 changed files with 165 additions and 152 deletions

View File

@ -8,12 +8,14 @@ description: Enforces Object Calisthenics principles for business domain code to
> Examples may be added later if needed. > Examples may be added later if needed.
## Objective ## Objective
This rule enforces the principles of Object Calisthenics to ensure clean, maintainable, and robust code in the backend, **primarily for business domain code**. 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 ## Scope and Application
- **Primary focus**: Business domain classes (aggregates, entities, value objects, domain services) - **Primary focus**: Business domain classes (aggregates, entities, value objects, domain services)
- **Secondary focus**: Application layer services and use case handlers - **Secondary focus**: Application layer services and use case handlers
- **Exemptions**: - **Exemptions**:
- DTOs (Data Transfer Objects) - DTOs (Data Transfer Objects)
- API models/contracts - API models/contracts
- Configuration classes - Configuration classes
@ -22,7 +24,6 @@ This rule enforces the principles of Object Calisthenics to ensure clean, mainta
## Key Principles ## Key Principles
1. **One Level of Indentation per Method**: 1. **One Level of Indentation per Method**:
- Ensure methods are simple and do not exceed one level of indentation. - 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**: 2. **Don't Use the ELSE Keyword**:
- Avoid using the `else` keyword to reduce complexity and improve readability. - 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: Sample Fail fast principle:
```csharp ```csharp
public void ProcessOrder(Order order) { public void ProcessOrder(Order order) {
if (order == null) throw new ArgumentNullException(nameof(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; this.value = value;
} }
} }
``` ```
4. **First Class Collections**: 4. **First Class Collections**:
- Use collections to encapsulate data and behavior, rather than exposing raw data structures. - 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(); .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 5. **One Dot per Line**:
public class UserCreator { - Limit the number of method calls in a single line to improve readability and maintainability.
public void CreateUser(string name) { /*...*/ }
}
public class UserDeleter {
public void DeleteUser(int id) { /*...*/ }
}
public class UserUpdater { ```csharp
public void UpdateUser(int id, string name) { /*...*/ } // Bad Example - Multiple dots in a single line
} public void ProcessOrder(Order order) {
``` var userEmail = order.User.GetEmail().ToUpper().Trim();
// Do something with userEmail
}
8. **No Classes with More Than Two Instance Variables**: // Good Example - One dot per line
- Encourage classes to have a single responsibility by limiting the number of instance variables. public void ProcessOrder(Order order) {
- Limit the number of instance variables to two to maintain simplicity. var user = order.User;
- Do not count ILogger or any other logger as instance variable. var email = user.GetEmail();
var userEmail = email.ToUpper().Trim();
```csharp // Do something with userEmail
// 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) { 6. **Don't abbreviate**:
this.userRepository = userRepository; - Use meaningful names for classes, methods, and variables.
this.emailService = emailService; - Avoid abbreviations that can lead to confusion.
this.logger = logger;
this.smsService = smsService;
}
}
// Good: Class with two instance variables ```csharp
public class UserCreateCommandHandler { // Bad Example - Abbreviated names
private readonly IUserRepository userRepository; public class U {
private readonly INotificationService notificationService; public string N { get; set; }
private readonly ILogger logger; // This is not counted as instance variable }
// Good Example - Meaningful names
public class User {
public string Name { get; set; }
}
```
public UserCreateCommandHandler(IUserRepository userRepository, INotificationService notificationService, ILogger logger) { 7. **Keep entities small (Class, method, namespace or package)**:
this.userRepository = userRepository; - Limit the size of classes and methods to improve code readability and maintainability.
this.notificationService = notificationService; - Each class should have a single responsibility and be as small as possible.
this.logger = logger;
} Constraints:
} - Maximum 10 methods per class
``` - Maximum 50 lines per class
- Maximum 10 classes per package or namespace
9. **No Getters/Setters in Domain Classes**:
- Avoid exposing setters for properties in domain classes. ```csharp
- Use private constructors and static factory methods for object creation. // Bad Example - Large class with multiple responsibilities
- **Note**: This rule applies primarily to domain classes, not DTOs or data transfer objects. public class UserManager {
public void CreateUser(string name) { /*...*/ }
```csharp public void DeleteUser(int id) { /*...*/ }
// Bad Example - Domain class with public setters public void SendEmail(string email) { /*...*/ }
public class User { // Domain class }
public string Name { get; set; } // Avoid this in domain classes
} // Good Example - Small classes with single responsibility
public class UserCreator {
// Good Example - Domain class with encapsulation public void CreateUser(string name) { /*...*/ }
public class User { // Domain class }
private string name; public class UserDeleter {
private User(string name) { this.name = name; } public void DeleteUser(int id) { /*...*/ }
public static User Create(string name) => new User(name); }
}
public class UserUpdater {
// Acceptable Example - DTO with public setters public void UpdateUser(int id, string name) { /*...*/ }
public class UserDto { // DTO - exemption applies }
public string Name { get; set; } // Acceptable for DTOs ```
}
``` 8. **No Classes with More Than Two Instance Variables**:
- Encourage classes to have a single responsibility by limiting the number of instance variables.
## Implementation Guidelines - Limit the number of instance variables to two to maintain simplicity.
- **Domain Classes**: - Do not count ILogger or any other logger as instance variable.
- Use private constructors and static factory methods for creating instances.
- Avoid exposing setters for properties. ```csharp
- Apply all 9 rules strictly for business domain code. // Bad Example - Class with multiple instance variables
public class UserCreateCommandHandler {
- **Application Layer**: // Bad: Too many instance variables
- Apply these rules to use case handlers and application services. private readonly IUserRepository userRepository;
- Focus on maintaining single responsibility and clean abstractions. private readonly IEmailService emailService;
private readonly ILogger logger;
- **DTOs and Data Objects**: private readonly ISmsService smsService;
- 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. public UserCreateCommandHandler(IUserRepository userRepository, IEmailService emailService, ILogger logger, ISmsService smsService) {
this.userRepository = userRepository;
- **Testing**: this.emailService = emailService;
- Ensure tests validate the behavior of objects rather than their state. this.logger = logger;
- Test classes may have relaxed rules for readability and maintainability. this.smsService = smsService;
}
- **Code Reviews**: }
- Enforce these rules during code reviews for domain and application code.
- Be pragmatic about infrastructure and DTO code. // Good: Class with two instance variables
public class UserCreateCommandHandler {
## References private readonly IUserRepository userRepository;
- [Object Calisthenics - Original 9 Rules by Jeff Bay](https://www.cs.helsinki.fi/u/luontola/tdd-2009/ext/ObjectCalisthenics.pdf) private readonly INotificationService notificationService;
- [ThoughtWorks - Object Calisthenics](https://www.thoughtworks.com/insights/blog/object-calisthenics) private readonly ILogger logger; // This is not counted as instance variable
- [Clean Code: A Handbook of Agile Software Craftsmanship - Robert C. Martin](https://www.oreilly.com/library/view/clean-code-a/9780136083238/)
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/)

View File

@ -104,7 +104,17 @@ function processFile(filePath) {
continue; 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; changed = true;
const innerTicksLen = ticksLen; const innerTicksLen = ticksLen;
lines[i] = ' ' + lines[i]; lines[i] = ' ' + lines[i];