
Example 1: Excessive Use of Single Responsibility Principle (SRP)
Description
Applying the Single Responsibility Principle too strictly can lead to classes and methods that do very little, resulting in an overwhelming number of classes and overly fragmented code.
Code Example
<?php
// Over-engineered example with too many small classes.
class UserValidator {
public function validateEmail(string $email): bool {
// validation logic
return true;
}
public function validatePassword(string $password): bool {
// validation logic
return true;
}
}
class UserService {
private $userValidator;
public function __construct(UserValidator $userValidator) {
$this->userValidator = $userValidator;
}
public function registerUser(string $email, string $password): void {
if ($this->userValidator->validateEmail($email) && $this->userValidator->validatePassword($password)) {
// registration logic
}
}
}
?>
Analysis
- SRP: Each class has a single responsibility, but this results in an overly complex design for a simple user registration process.
- Impact: Increased complexity and maintenance difficulty without significant benefits.
- Point of reflection: Do you really need to validate every single parameter individualy? A single method validate will hurt the principles of SOLID?
Example 2: Overuse of Interface Segregation Principle (ISP)
Description
Creating too many interfaces to adhere to the Interface Segregation Principle can lead to excessive boilerplate code and unnecessary complexity.
Code Example
<?php
// Over-engineered example with too many interfaces.
interface EmailValidatorInterface {
public function validateEmail(string $email): bool;
}
interface PasswordValidatorInterface {
public function validatePassword(string $password): bool;
}
class UserValidator implements EmailValidatorInterface, PasswordValidatorInterface {
public function validateEmail(string $email): bool {
// validation logic
return true;
}
public function validatePassword(string $password): bool {
// validation logic
return true;
}
}
?>
Analysis
- ISP: Interfaces are segregated, but this leads to an explosion of interfaces and implementations for simple validation tasks.
- Impact: Increased complexity and reduced readability without substantial benefits.
- Point of reflection: How many interfaces a single user will implement? What’s the need of many interfaces for a small scope?
Example 3: Rigid Application of GRASP Patterns
Description
Over-application of GRASP patterns like Information Expert and Creator can result in convoluted designs with excessive delegation and indirection.
Code Example
<?php
// Over-engineered example with excessive delegation.
class Order {
private $customer;
public function __construct(Customer $customer) {
$this->customer = $customer;
}
public function placeOrder(): void {
$this->customer->createOrder();
}
}
class Customer {
public function createOrder(): void {
// order creation logic
}
}
?>
Analysis
- GRASP: The Information Expert pattern is applied, but it results in unnecessary delegation and indirection.
- Impact: Reduced clarity and increased complexity without clear benefits.
Example 4: Overly Granular Methods
Description
Creating methods that do very little and have long names in an attempt to follow SOLID principles can lead to difficult-to-read and maintain code.
Code Example
<?php
// Over-engineered example with overly granular methods.
class UserService {
public function registerUser(string $email, string $password): void {
if ($this->isEmailValid($email) && $this->isPasswordValid($password)) {
$this->saveUserToDatabase($email, $password);
}
}
private function isEmailValid(string $email): bool {
// validation logic
return true;
}
private function isPasswordValid(string $password): bool {
// validation logic
return true;
}
private function saveUserToDatabase(string $email, string $password): void {
// save logic
}
}
?>
Analysis
- SOLID: Methods follow the Single Responsibility Principle, but they are overly granular with long names.
- Impact: Decreased readability and increased complexity without significant benefits.
Example 5: Excessive Abstraction with Interfaces and Abstract
Description
- Scenario: You have a simple data processing task. You create an interface for every possible data source, even if you only have one or two implementations. Then, you create abstract classes for each step of the processing, with each abstract class having a single abstract method.
- Over-engineering:
- Creating interfaces and abstract classes for every tiny variation, even when there’s no foreseeable need for further extension.
- Resulting in a proliferation of classes and interfaces that add little value.
- SoC and SOLID:
- While SoC encourages separating responsibilities, and the Open/Closed Principle (from SOLID) encourages extension over modification, overdoing it can hinder readability.
- The Single Responsibility Principle (SRP) can be misapplied by creating classes with excessively granular responsibilities, leading to one-line methods.
- Example:
- Instead of a simple
CsvProcessor
class, you might have:IDataSource
ICsvDataSource : IDataSource
IDataTransformer
ICsvDataTransformer : IDataTransformer
IDataValidator
ICsvDataValidator : IDataValidator
AbstractDataProcessor
AbstractCsvProcessor : AbstractDataProcessor
ConcreteCsvProcessor : AbstractCsvProcessor
- And each interface and abstract class might contain only one or two methods.
- Instead of a simple
Analysis
- Grasp: Information Expert can be misapplied, by assigning very small amounts of information to a large amount of classes. High Cohesion is good, but when overdone, it can lead to too many classes. Low coupling can be overdone creating to many interfaces.
- Highlight: While interfaces and abstract classes are crucial for extensibility, start with concrete implementations and introduce abstractions only when necessary. The “YAGNI” (You Ain’t Gonna Need It) principle is relevant here.
Example 6: Overly Verbose Naming
Description
- Scenario: Every method and class has an extremely long, descriptive name, even for simple operations.
- Over-engineering:
- Making code difficult to read and understand.
- Increasing the cognitive load on developers.
- SoC and SOLID:
- While clear naming is important, excessively long names can hinder readability.
- Example:
DataTransferObjectForCustomerInformationRetrievalAndValidation
instead ofCustomerDto
.ProcessAndValidateAndPersistCustomerDataToDatabaseAfterRetrievalFromExternalSource
instead ofProcessCustomer
.
Analysis
- Highlight:
- Use concise and meaningful names that accurately reflect the purpose of the code.
Conclusion
Nowadays most of developers are worried about SOLID, GOF, GRASP and design principles which is very good. But when you don’t have experience it’s easy get confuse while applying the principles.
Simple solutions are not bad solutions, it’s hard to say that a solution is too simple. Instead of saying that try to find where the solution is broking the principles.
Pinpoint which class and which method couldn’t have certain responsability applied to them.
Make sure that redesign will have clear benefits. Complexity is not equal good design and organization.
While SOLID and GRASP principles are valuable for creating maintainable and scalable software, over-applying them can lead to over-engineering. It is crucial to strike a balance and apply these principles judiciously to avoid unnecessary complexity.