Tip: AI Copilot that follows this document: https://chatgpt.com/g/g-6737e2027dbc8191a00c2a73abf7907e-codelight
Why We Need the Codelight Manifesto
Imagine building a house without standardized measurements for doors or windows. Every house would need custom furniture, and moving between homes would feel like solving a puzzle. Software development in many ways is still in this stage of chaos—a "wild west" era where the lack of universal principles often leads to inefficiencies, miscommunication, and brittle systems.
Take this example:
- A file upload system that doesn't sanitize file names or validate file types.
- An API integration where no consistent error-handling exists.
- A legacy system patched so many times that no one knows how it works anymore.
Every developer has encountered these scenarios, and they happen because we lack clear baselines of responsibility—principles that hold us accountable for our code. The Codelight Manifesto aims to address this by combining timeless coding wisdom, modern best practices, and AI collaboration workflows.
A Vision of Responsibility
The manifesto sets the bar for minimum coding standards, much like how engineers rely on structural integrity for bridges or how electricians ensure homes are grounded against surges. Here's what it offers:
- C | Clarity: Code should explain itself, like a well-placed road sign.
- O | Optimization: Systems should run efficiently but never at the cost of correctness.
- D | Documentation: Good code whispers its purpose; great documentation shouts it.
- E | Error Handling: Silent failures are silent disasters. Always know what went wrong.
- L | Loose Coupling: Systems should work together without unnecessary dependency.
- I | Integration Testing: Test integration as you build, not as an afterthought.
- G | Generics: Create reusable, type-safe components.
- H | Human-Centric Design: Code is for humans to maintain, not just for machines to execute.
- T | The Workflow Integration: Build incrementally, test constantly, refine endlessly.
What About AI?
AI can be your co-pilot, but only if you teach it the rules of the road. Here's a practical illustration:
Imagine you're using AI to build a payment processor. If the AI suggests a calculation for sales tax, will it consider edge cases like rounding errors, international tax rates, or API failures? Without guidance, it likely won't. That’s where Codelight principles come in, ensuring the system is robust and maintainable.
Real-World Context
Let’s ground this in an example:
- Before Codelight: A developer writes a function to calculate tax without handling edge cases, rounding inconsistencies, or invalid input.
function calculateTax($amount, $rate)
{
return $amount * $rate;
}
This function might work until it doesn’t. What happens if $amount is a string? What if the calculation requires precision for financial systems? What if negative rates are accidentally passed?
- After Codelight:
use Brick\Math\BigDecimal;
/**
* Calculate tax with precision and safety.
*
* @param non-empty-string&numeric-string $amount
* @param non-empty-string&numeric-string $rate
* @return non-empty-string&numeric-string
*/
function calculateTax(string $amount, string $rate): string
{
$amountDecimal = BigDecimal::of($amount);
$rateDecimal = BigDecimal::of($rate);
return $amountDecimal
->multipliedBy($rateDecimal)
->toScale(2)
->toString();
}
With Codelight, the system now handles precision, type safety, and extensibility. It’s future-proofed.
The Human-AI Partnership
The Codelight Manifesto encourages developers to harness AI for suggestions, but always validate its output against these principles. Ask AI:
- Can you simplify this logic while retaining clarity?
- Are edge cases for this function adequately handled?
- What common pitfalls should I watch for here?
By doing so, you elevate your work from simply "coding" to crafting robust, scalable, and maintainable solutions.
The Enhanced Principles
- Clarity: Illuminate intent with explicit, consistent, and structured code and documentation.
- Optimize Standards: Build on universal conventions and adopt platform-agnostic solutions.
- Design for Resilience: Fail gracefully by preparing for errors and reducing complexity.
- Embrace Simplicity: Simplify complexity through modular design and readability.
- Learn from History: Avoid repeating mistakes by capturing and applying lessons systematically.
- Innovate Within Boundaries: Balance creativity with constraints like security and maintainability.
- Guard Against Vulnerabilities: Proactively secure systems with security-by-design principles.
- Harmonize Teams and Tools: Align processes and tools for collaboration and efficiency.
- Test Beyond Perfection: Measure testing effectiveness to ensure resilience and robustness.
Mantra: "If it's not clear, it's broken."
Why Clarity Matters
Code is read more often than it’s written. Developers—future you included—shouldn’t have to guess what your code does. Ambiguity in code leads to bugs, inefficiencies, and wasted time. A function's name, parameters, and structure should be as descriptive as a user manual for a life-saving device.
Real-World Problem
Imagine you encounter this function:
function process($data)
{
return $data * 2;
}
- What does "process" mean?
- What is $data supposed to be?
- Why is it multiplied by 2?
This kind of code is a black box, a breeding ground for errors and misunderstandings.
Codelight Solution
Clarity transforms this mess into something meaningful. Here's the improved version:
/**
* Double the provided integer value.
*
* @param int $value The integer to be doubled.
* @return int The doubled value.
*/
function doubleValue(int $value): int
{
return $value * 2;
}
Key Changes:
- Explicit Function Name: doubleValue describes the exact purpose.
- Strict Typing: The parameter and return type ensure clarity and prevent misuse.
- PHPDoc Annotations: Add context for the developer, such as what the parameter represents.
AI Collaboration Tips
- Naming Suggestions: Use AI to brainstorm meaningful function names.
Example prompt: "Suggest descriptive names for a function that calculates compound interest." - Code Simplification: Ask the AI to identify unclear or redundant code.
Example prompt: "Is there a simpler way to achieve this result while making the code more readable?"
Edge Case Example
Even with clear naming, the implementation must address edge cases. Here’s an expanded example:
/**
* Calculate tax with precision and safety.
*
* @param (non-empty-string&numeric-string) $amount The taxable amount as a numeric string.
* @param (non-empty-string&numeric-string) $rate The tax rate as a decimal string (e.g., '0.20' for 20%).
* @return (non-empty-string&numeric-string) The calculated tax, rounded to two decimal places.
*/
function calculateTax(string $amount, string $rate): string
{
$amountDecimal = Brick\Math\BigDecimal::of($amount);
$rateDecimal = Brick\Math\BigDecimal::of($rate);
return $amountDecimal
->multipliedBy($rateDecimal)
->toScale(2, Brick\Math\RoundingMode::HALF_UP)
->toString();
}
How Clarity Saves the Day
This approach ensures:
- Future-proofing: Developers won’t need to decipher or refactor ambiguous logic.
- Error Prevention: Misunderstandings about the code's purpose are eliminated.
- Maintainability: Clear naming and strict typing guide enhancements and bug fixes.
Mantra: "Fast is better than slow, but correctness comes first."
Why Optimization Matters
Performance bottlenecks can cripple applications, especially at scale. However, premature optimization—tweaking code before it's proven necessary—introduces unnecessary complexity. Balance is key: build correct systems first, then optimize based on evidence, not assumptions.
Real-World Problem
Consider this function for summing numbers:
function calculateSum(array $numbers)
{
$sum = 0;
foreach ($numbers as $number) {
$sum += $number;
}
return $sum;
}
While it works, it’s verbose and duplicates functionality already available in PHP. Worse, there's no validation for input types or structure.
Codelight Solution
Leverage PHP’s built-in functions for simplicity and performance:
/**
* Calculate the sum of integers in a list.
*
* @param non-empty-list<int> $numbers A non-empty list of integers.
* @return int The total sum of the numbers.
*/
function sumArray(array $numbers): int
{
return array_sum($numbers);
}
PS: In the end you should use array_sum
directly because it's better supported by IDE and static analyse tools than your own wrapper.
Key Changes:
- Native Functionality: array_sum is optimized and avoids reinventing the wheel.
- Strict Typing: Using non-empty-list<int> ensures valid input.
- Clarity: The function name and type hint make its purpose unambiguous.
AI Collaboration Tips
- Profiling Performance: Use AI to suggest optimization opportunities after profiling. Example prompt: "Analyze this code for potential bottlenecks in sorting a large array."
- Best Practices: Ask AI to compare alternative implementations. Example prompt: "Which approach is faster for summing numbers in PHP, looping manually or using array_sum?"
Avoiding Premature Optimization
Here’s a deeper look at premature optimization pitfalls:
/**
* Sort an array using bubble sort (inefficient example).
*
* @param list<int> $numbers A list of integers.
* @return list<int> The sorted list of integers.
*/
function bubbleSort(array $numbers): array
{
$n = count($numbers);
for ($i = 0; $i < $n; $i++) {
for ($j = 0; $j < $n - $i - 1; $j++) {
if ($numbers[$j] > $numbers[$j + 1]) {
$temp = $numbers[$j];
$numbers[$j] = $numbers[$j + 1];
$numbers[$j + 1] = $temp;
}
}
}
return $numbers;
}
While bubble sort works, it’s a poor choice for sorting large datasets. Instead, rely on PHP’s built-in sort function:
/**
* Sort a list of integers in ascending order.
*
* @param list<int> $numbers A list of integers.
* @return list<int> The sorted list of integers.
*/
function sortArray(array $numbers): array
{
sort($numbers);
return $numbers;
}
How Optimization Improves Code
- Performance Gains: Use optimized native functions where possible.
- Correctness First: Validate functionality before diving into performance.
- Focus on Clarity: Optimized code should remain readable.
Mantra: "Good code is self-documenting, but explicit documentation adds value."
Why Documentation Matters
While clear code should minimize the need for documentation, explicit annotations clarify intent and assumptions. Good documentation bridges the gap between what code does and why it does it, making onboarding, debugging, and collaboration more efficient.
Real-World Problem
Consider this function:
function sendMessage($recipient, $message)
{
if (filter_var($recipient, FILTER_VALIDATE_EMAIL)) {
mail($recipient, "Notification", $message);
}
}
Issues:
- Ambiguity: What type of $recipient is expected? An email? A phone number?
- Assumptions: The function silently fails if the email is invalid.
- Missing Context: There’s no explanation of the function's behavior or parameters.
Codelight Solution
Enhance clarity with proper PHPDocs, strict types, and meaningful comments:
/**
* Send an email notification to a user.
*
* @param literal-string $email The recipient's email address.
* @param non-empty-string $message The message content.
* @return bool True if the email was sent successfully, false otherwise.
*
* @throws InvalidArgumentException If the email format is invalid.
*/
function sendEmailNotification(string $email, string $message): bool
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('Invalid email address.');
}
return mail($email, 'Notification', $message);
}
Key Improvements:
- Explicit Types: literal-string and non-empty-string ensure data validity.
- Descriptive PHPDocs: Clearly define the purpose, parameters, and behavior.
- Exception Handling: Replace silent failures with actionable errors.
AI Collaboration Tips
- AI for PHPDoc Suggestions: Use AI to draft PHPDoc annotations for existing functions.
Example prompt: "Generate PHPDoc for a function that sends email notifications in PHP." - Explain Complex Code: Ask AI to document intricate logic. Example prompt: "Explain this recursive function for traversing a tree structure in PHP."
Improving Self-Documenting Code
Beyond explicit documentation, strive for self-documenting practices:
- Descriptive Names: Rename ambiguous variables and functions for clarity.
Example: Change sendMessage to sendEmailNotification. - Avoid Overloading: Keep functions single-purpose.
Example: A sendNotification function that handles emails, SMS, and push notifications can become confusing. - Leverage Type Declarations: Combine strict types with PHPStan annotations for full clarity.
How Documentation Improves Code
- Clarifies Intent: Explains why code exists, not just how it works.
- Supports Maintenance: Eases debugging and future modifications.
- Facilitates Collaboration: Helps teams understand and use your code.
Mantra: "Silent failures are silent disasters."
Why Error Handling Matters
Silent errors can wreak havoc on systems, making issues difficult to diagnose and resolve. A robust error-handling strategy ensures developers and systems can detect, respond to, and recover from unexpected states.
Real-World Problem
Consider this function:
function getUserData(int $id)
{
$user = fetchFromDatabase($id);
if (!$user) {
return null; // Silent failure.
}
return $user;
}
PS: if we want to do something like this, we should rename the function into e.g. getUserDataIfExists(int $id): ?array
Issues:
- Ambiguity: The function may return null for valid or invalid states.
- Silent Failure: There's no indication why fetching the user failed.
Codelight Solution
Explicit exceptions and clear error handling:
/**
* Retrieve user data by ID.
*
* @param positive-int $id The unique identifier for the user.
* @return list<UserData> The user data as an associative array.
*
* @throws UserNotFoundException If the user with the given ID does not exist.
*/
function getUserData(int $id): array
{
$user = fetchFromDatabase($id, UserData::class);
if (!$user) {
throw new UserNotFoundException("User with ID $id not found.");
}
return $user;
}
/**
* Fetch data from the database by ID.
*
* @template T of object
* @param positive-int $id The unique identifier for the record.
* @param class-string<T> $className The class name of the object to hydrate.
* @return null|T The hydrated object if found, or null if not.
*
* @throws DatabaseConnectionException If the database connection fails.
* @throws InvalidQueryException If the query execution fails.
*/
function fetchFromDatabase(int $id, string $className): ?object
{
// ...
}
Key Improvements:
- Explicit Exceptions: Use specific exception classes like UserNotFoundException to make errors descriptive.
- Positive Type Checking: The parameter uses positive-int to ensure valid IDs.
- Meaningful Responses: Developers know exactly what went wrong.
Enhancing with Logs
Logging complements exceptions by creating an auditable trail of errors:
/**
* @return list<UserData>
*/
function getUserData(int $id): array
{
$user = fetchFromDatabase($id, UserData::class);
if (!$user) {
throw new UserNotFoundException("User with ID $id not found.");
}
return $user;
}
/**
* @return list<UserData>
*/
function getUserDataIfExists(int $id): ?array
{
try {
getUserData($id)
} catch (UserNotFoundException $e) {
return null;
}
}
try {
getUserData(1); // UserData or Exception
} catch (UserNotFoundException $e) {
error_log($e->getMessage());
}
getUserDataIfExists(1); // UserData or NULL
AI Collaboration Tips
- Error Identification: Use AI to scan for potential silent failures.
Example prompt: "Review this function for error-handling improvements in PHP." - Propose Exception Classes: Ask AI to suggest specific exception hierarchies.
Example prompt: "Create exception classes for a user management system."
Handling User-Fixable Errors
When user action can resolve an issue, provide clear feedback:
/**
* Update a user's email address.
*
* @param positive-int $id The unique identifier for the user.
* @param literal-string $email The new email address.
* @return bool True if the update was successful.
*
* @throws InvalidArgumentException If the email format is invalid.
* @throws UserNotFoundException If the user ID is not found.
*/
function updateUserEmail(int $id, string $email): bool
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException("Invalid email format: $email");
}
$user = fetchFromDatabase($id);
if (!$user) {
throw new UserNotFoundException("User with ID $id not found.");
}
return saveToDatabase($id, ['email' => $email]);
}
Benefits:
- Clear User Guidance: The exception message directly highlights the issue.
- Dual-Level Errors: Combines developer-facing and user-facing errors effectively.
Why Error Handling is Non-Negotiable
- Improves Debugging: Traceable errors make debugging faster and more efficient.
- Prevents Catastrophic Failures: Controlled errors prevent the system from collapsing.
- Enables Proactive Monitoring: Logs and exceptions facilitate real-time issue detection.
Mantra: "Systems should be easy to extend and hard to break."
Why Loose Coupling Matters
Tightly coupled systems are fragile and challenging to extend. Changes in one part can cascade through the codebase, increasing the risk of bugs. Loose coupling ensures components interact with clear contracts, promoting maintainability and scalability.
Real-World Problem
Consider this tightly coupled notification system:
function sendNotification(string $email, string $message): bool
{
return mail($email, "Notification", $message);
}
Issues:
- Rigid Dependency: The function is limited to email notifications.
- No Flexibility: Adding another notification type, like SMS, requires rewriting the function.
Codelight Solution
Decouple components using interfaces and dependency injection:
interface NotifierInterface
{
public function send(NotifierRecipientInterface $recipient, string $message): bool;
}
/**
* Email notifier implementation.
*/
final class EmailNotifier implements NotifierInterface
{
public function send(NotifierRecipientInterface $recipient, string $message): bool {
// Implement mail sending logic ...
}
}
/**
* SMS notifier implementation.
*/
final class SmsNotifier implements NotifierInterface
{
public function send(NotifierRecipientInterface $recipient, string $message): bool {
// Implement SMS sending logic ...
}
}
/**
* Notification manager to handle different notifiers.
*/
final class NotificationManager
{
public function __construct(private NotifierInterface $notifier) { }
public function notify(NotifierRecipientInterface $recipient, string $message): bool {
return $this->notifier->send($recipient, $message);
}
}
Key Improvements
- Interfaces: NotifierInterface defines a contract, enabling multiple implementations without changing the interface.
- Dependency Injection: NotificationManager uses dependency injection for flexibility.
- Extensibility: Adding new notification types, like push notifications, requires minimal changes.
AI Collaboration Tips
- Refactoring Suggestions: Ask AI to identify tightly coupled components.
Example prompt: "Refactor this notification system to use interfaces and dependency injection." - Test Coverage: Use AI to suggest test cases for each notifier implementation.
Example prompt: "Generate unit tests for EmailNotifier and SmsNotifier classes."
Real-World Extensibility
Imagine adding a new PushNotifier:
final class PushNotifier implements NotifierInterface
{
public function send(NotifierRecipientInterface $recipient, string $message): bool {
// Implement push notification logic.
}
}
// Usage
$recipient = User::createFromArray(getUserData(1, UserData::class));
$pushNotifier = new PushNotifier();
$notificationManager = new NotificationManager($pushNotifier);
$notificationManager->notify($recipient, 'Hello, Push Notification!');
Benefits:
- Isolated Logic: Each notifier is independent of the others.
- Single Responsibility: Each class adheres to the Single Responsibility Principle (SRP).
- Scalable Architecture: The system evolves without breaking existing functionality.
Why Loose Coupling is Essential
- Simplifies Maintenance: Changes to one component don't ripple through the system.
- Promotes Testing: Isolated components are easier to test.
- Facilitates Collaboration: Teams can work on independent components without conflicts.
Mantra: "Test as you go; integration can't wait."
Why Integration Testing Matters
While unit tests validate individual components, integration tests ensure these components work cohesively. Skipping integration testing can lead to unexpected failures when components interact, especially in loosely coupled systems.
Real-World Problem
Imagine a notification system with multiple notifiers. Unit tests confirm each notifier works individually, but do they work when integrated with a manager?
Without integration tests, bugs like misconfigured dependencies or data mismatches can remain hidden until production.
Codelight Solution
Design integration tests alongside features to validate inter-component interactions.
Example: Integration Testing a Notification System
Using PHPUnit:
use PHPUnit\Framework\TestCase;
final class NotificationManagerTest extends TestCase
{
public function testEmailNotificationSendsSuccessfully(): void
{
$notifier = new EmailNotifier();
$notificationManager = new NotificationManager($notifier);
$result = $notificationManager->notify(new DummyRecipient(), 'Test Email Message');
$this->assertTrue($result, 'Email notification failed.');
}
public function testSmsNotificationSendsSuccessfully(): void
{
$notifier = new SmsNotifier();
$notificationManager = new NotificationManager($notifier);
$result = $notificationManager->notify(new DummyRecipient(), 'Test SMS Message');
$this->assertTrue($result, 'SMS notification failed.');
}
}
Key Improvements
- End-to-End Validation: Tests cover the interaction between NotificationManager and notifiers.
- Error Detection: Catch misconfigurations, like invalid dependencies or method mismatches.
- System Confidence: Ensure components perform reliably in real-world scenarios.
AI Collaboration Tips
- Generate Test Cases: Use AI to scaffold integration tests for new components.
- Example prompt: "Create integration tests for a notification system with email and SMS notifiers."
- Edge Case Identification: Ask AI to identify potential integration failures.
- Example prompt: "Suggest edge cases for testing a notification manager with multiple notifiers."
- Refactor Tests: Leverage AI to streamline test setups or improve readability.
Real-World Example: Expanding the System
Adding a PushNotifier? Extend the integration tests:
public function testPushNotificationSendsSuccessfully(): void
{
$notifier = new PushNotifier();
$notificationManager = new NotificationManager($notifier);
$result = $notificationManager->notify(new DummyRecipient(), 'Test Push Message');
$this->assertTrue($result, 'Push notification failed.');
}
Best Practices for Integration Testing
- Cover All Use Cases: Ensure tests validate each possible interaction.
- Test Dependencies: Validate that injected dependencies behave as expected.
- Mock External Services: Use stubs or mocks for APIs or databases to isolate testing from external factors.
Why Integration Testing is Essential
- Confidence in Deployments: Reduce the risk of production issues.
- Improved System Resilience: Ensure components gracefully handle unexpected inputs or failures.
- Faster Debugging: Pinpoint integration points causing issues.
Mantra: "Reusable components are better than rigid ones."
Why Generics Matter
Generics allow for flexible and reusable code while maintaining strict type safety. They provide clarity by defining the relationships between input and output types, reducing bugs and improving developer experience (DX).
Real-World Problem
You have a collection of items and need to filter, sort, or retrieve elements without losing type information. Without generics, you'd either sacrifice type safety or repeat code for different types.
Codelight Solution
Leverage generics to write reusable, type-safe functions and classes.
Example: Filtering a List of Items
Using PHPStan's generic syntax:
/**
* @template T
*
* @param list<T> $items
* @param callable(T): bool $filterCallback
* @return list<T>
*/
function filterItems(array $items, callable $filterCallback): array
{
return array_values(array_filter($items, $filterCallback));
}
Usage Example:
$numbers = [1, 2, 3, 4, 5];
$evenNumbers = filterItems($numbers, fn(int $num): bool => $num % 2 === 0);
// Result: [2, 4]
$strings = ['apple', 'banana', 'cherry'];
$filteredStrings = filterItems($strings, fn(string $str): bool => str_contains($str, 'a'));
// Result: ['apple', 'banana']
Key Improvements
- Reusability: The same function works for any data type, thanks to the @template T annotation.
- Type Safety: Explicit PHPDocs ensure static analysis tools catch errors during development.
- Clarity: Developers can easily understand the intended use and constraints of the function.
AI Collaboration Tips
- Convert Existing Code: Use AI to refactor rigid methods into reusable, generic ones.
- Example prompt: "Refactor this sorting function to use generics with type-safe PHPDoc."
- Generate Templates: Ask AI to scaffold generic functions or classes for common use cases.
- Example prompt: "Create a generic repository class for managing entities in PHP."
- Identify Edge Cases: Use AI to suggest scenarios where generics improve maintainability.
- Example prompt: "What are the benefits of using generics for a collection of user objects?"
Real-World Example: A Generic Repository
Managing entities in a database often requires reusable repository classes. Here's a generic implementation:
/**
* @template T of Entity
* @template TId of scalar
*/
interface RepositoryInterface
{
/**
* @param TId $id
* @return T|null
*/
public function find($id): ?object;
/**
* @param T $entity
*/
public function save(object $entity): void;
}
/**
* @implements RepositoryInterface<User, int>
*/
final class UserRepository implements RepositoryInterface
{
public function find(int $id): ?User {
// Find user logic...
}
public function save(object $entity): void {
// Save user logic...
}
}
Best Practices for Generics
- Define Relationships: Use PHPStan's @template, @extends, and @implements annotations to clarify input-output relationships.
- Validate Assumptions: Use generics to enforce constraints (e.g., T of Entity ensures a class belongs to a specific hierarchy).
- Leverage Static Analysis: Generics shine when used with tools like PHPStan to catch type mismatches.
Why Generics are Essential
- Improved DX: Developers can use generic functions without worrying about type errors.
- Less Redundant Code: Reduce repetitive boilerplate for similar use cases.
- Future-Proof Systems: Generics adapt to new requirements with minimal refactoring.
Mantra: "Code is for humans first, machines second."
Why Human-Centric Design Matters
Code is read far more often than it is written. Prioritizing readability and maintainability ensures that your software can adapt to future requirements while remaining comprehensible to other developers—or even yourself—months or years later.
Real-World Problem
Complex, overly optimized code may run efficiently but becomes a nightmare for debugging or extending. Developers spend hours trying to decipher obscure logic, leading to wasted time and frustration.
Codelight Solution
Embrace human-centric principles by designing for clarity, simplicity, and maintainability. Use meaningful names, limit nested logic, and avoid clever shortcuts that obscure intent.
Example: Calculating Shipping Costs
Readable and maintainable code:
/**
* Calculate shipping costs based on weight and location.
*
* @param float $weight The weight of the package in kilograms.
* @param non-empty-string $destination The destination for delivery.
* @return float The calculated shipping cost.
*/
function calculateShippingCost(float $weight, string $destination): float
{
$baseCost = 5.00;
$weightMultiplier = 1.5;
$remoteFee = str_contains($destination, 'remote') ? 10.00 : 0.00;
return $baseCost + ($weight * $weightMultiplier) + $remoteFee;
}
Key Features:
- Simple Structure: Clear variable names and straightforward calculations.
- Human Context: Factors like remoteFee explicitly convey why certain costs are added.
- Future-Proof: Easy to extend (e.g., adding regional adjustments).
AI Collaboration Tips
- Explain the Code: Use AI to generate plain-language explanations for complex logic.
- Example prompt: "Explain this function to a junior developer in simple terms."
- Suggest Alternatives: AI can propose cleaner ways to write convoluted code.
- Example prompt: "Refactor this nested loop for better readability and performance."
- Validate Readability: Use AI to analyze how readable your code is.
- Example prompt: "Does this code follow human-centric design principles?"
Real-World Example: A Billing System
Readable code for calculating total bills with discounts:
/**
* Calculate the total cost of a bill after applying discounts.
*
* @param non-empty-list<float> $itemPrices List of item prices.
* @param float $discount The discount percentage (e.g., 0.15 for 15%).
* @return float The total cost after applying the discount.
*/
function calculateTotalCost(array $itemPrices, float $discount): float
{
$total = array_sum($itemPrices);
$discountAmount = $total * $discount;
return $total - $discountAmount;
}
Best Practices for Human-Centric Design
- Use Descriptive Names: Replace single-letter variables with meaningful identifiers (e.g., $w → $weight).
- Break Down Logic: Simplify complex logic into smaller, reusable functions.
- Comment with Purpose: Avoid redundant comments; focus on why, not what.
- Favor Readability Over Cleverness: Choose clear, explicit logic over one-liners that obscure intent.
Why Human-Centric Design is Essential
- Improved Teamwork: Others can easily understand and contribute to your codebase.
- Reduced Technical Debt: Clear code is easier to debug, extend, and refactor.
- Sustainable Systems: Readability ensures that the software evolves gracefully.
Mantra: "A good process beats great intentions."
Why The Workflow Integration Matters
Even the most brilliant principles fail without effective implementation. A robust workflow bridges the gap between vision and execution, ensuring that coding principles are consistently applied, tested, and refined.
The Codelight Workflow integrates structured steps with flexibility for iterative improvement, aligning human efforts with AI capabilities.
The Codelight Workflow
- 🧐 Define the Problem
- Clearly articulate the goal, constraints, and intended outcome.
- Example Prompt for AI: "What are the common pitfalls when implementing a file upload system?"
- 🌳 Analyze the Root Cause
- Use tools like the 5 Whys to uncover underlying issues.
- Example: If users encounter frequent upload failures:
- Why? The files are too large.
- Why? The system lacks size validation.
- Why? Requirements were unclear.
- Solution: Add file size validation and update requirements.
- 📉 Break Down the Tasks
- Divide the solution into manageable pieces using Kanban-style planning:
- Define: Clarify requirements and risks.
- Design: Plan implementation details.
- Develop: Write and test code.
- Refine: Polish and optimize.
- Divide the solution into manageable pieces using Kanban-style planning:
- 🚀 Implement and Iterate
- Apply Codelight principles to each task, refining based on testing and feedback.
- AI Tip: Use AI to generate scaffolding for complex tasks or validate implementation against principles.
Workflow in Action: Implementing a File Upload Feature
Step 1: Define the Problem
- Goal: Allow users to upload files securely.
- Constraints: Accept only specific file types (e.g., PDF, PNG) and limit file size to 5 MB.
- Risks: Potential security vulnerabilities like file execution or directory traversal.
Step 2: Analyze the Root Cause
- Why? Security issues in uploads often arise from insufficient validation.
- Why? Developers assume uploaded files are safe by default.
- Solution: Implement strict validation and sanitization.
Step 3: Break Down the Tasks
- Define: List all requirements (e.g., file size/type limits, storage rules).
- Design: Outline validation and error-handling mechanisms.
- Develop: Implement features in phases.
- Refine: Test against edge cases, optimize, and document.
Step 4: Implement and Iterate
Secure File Upload Example:
/**
* Handle file uploads securely.
*
* @param array $file The uploaded file from $\_FILES.
* @return string The file path if the upload is successful.
* @throws RuntimeException If validation fails or the upload is unsuccessful.
*/
function handleFileUpload(array $file): string
{
// Validate file upload
if (!isset($file['error']) || is_array($file['error'])) {
throw new RuntimeException('Invalid file parameters.');
}
if ($file['error'] !== UPLOAD_ERR_OK) {
throw new RuntimeException('File upload error.');
}
// Validate file size (max 5MB)
if ($file['size'] > 5 * 1024 * 1024) {
throw new RuntimeException('Exceeded file size limit.');
}
// Validate file type
$finfo = new finfo(FILEINFO_MIME_TYPE);
$validTypes = ['application/pdf', 'image/png'];
$fileType = $finfo->file($file['tmp_name']);
if (!in_array($fileType, $validTypes, true)) {
throw new RuntimeException('Invalid file type.');
}
// Move file to a safe location
$uploadDir = '/var/uploads/';
$filename = bin2hex(random_bytes(8)) . '.' . pathinfo($file['name'], PATHINFO_EXTENSION);
$targetPath = $uploadDir . $filename;
if (!move_uploaded_file($file['tmp_name'], $targetPath)) {
throw new RuntimeException('Failed to move uploaded file.');
}
return $targetPath;
}
AI Collaboration Tips
- Risk Identification: Ask AI to suggest security risks for features like file uploads.
- Example Prompt: "What are common vulnerabilities in file upload implementations?"
- Validation and Testing: Use AI to generate test cases for edge scenarios.
- Example Prompt: "Create PHPUnit tests for this file upload function."
- Optimization Suggestions: Ask AI to analyze performance bottlenecks.
- Example Prompt: "How can I improve performance for handling large file uploads?"
Why a Workflow is Essential
- Ensures Consistency: By following clear steps, developers minimize oversight and errors.
- Encourages Collaboration: A shared workflow helps teams align on goals and practices.
- Enables Continuous Improvement: Iterative refinement fosters learning and adaptation.
A Path Forward
Software development today faces unique challenges. With increasing complexity, the need for scalable, maintainable, and human-centric systems is more critical than ever. The Codelight Manifesto bridges the gap between best practices and practical implementation, offering developers a roadmap for crafting code that is clear, robust, and future-proof.
Why This Matters
Software is more than lines of code; it’s the foundation of modern society. As developers, we carry the responsibility of building systems that are not just functional but also safe, maintainable, and adaptable.
- For Developers: This manifesto provides a framework for writing better code. It’s a commitment to clarity, collaboration, and continuous improvement.
- For Organizations: Following these principles reduces technical debt, improves system resilience, and fosters innovation.
- For AI Collaboration: By integrating workflows and best practices, AI becomes a powerful ally, enhancing developer productivity while maintaining high-quality standards.
What Lies Ahead
The future of software development will be defined by how we adapt to evolving challenges. Automation, AI, and collaborative tools are transforming the way we work, but the core principles of good engineering remain timeless.
- Focus on Human-Centric Design: The best code is written for people, not just machines.
- Embrace AI as a Partner: Use AI to amplify your strengths, not replace your judgment.
- Commit to Lifelong Learning: The tech landscape evolves rapidly. Staying curious and adaptable is key.
A Call to Action
The Codelight Manifesto is more than a set of principles—it’s a mindset. Adopt it. Challenge it. Refine it. Share it. Let’s build a future where software development is not just a craft but a discipline rooted in clarity, collaboration, and responsibility.
Closing Mantra
"Code is not just what we create; it’s how we think, how we collaborate, and how we shape the world."
Are you ready to combine the Codelight Manifesto with your development practices and take the first step toward better software?