Clean Code in TypeScript: Principles and Examples
In modern web development, TypeScript has become the de facto standard for building large and reliable applications. However, TypeScript itself does not guarantee code quality. To keep a project flexible, understandable, and easily maintainable, it is necessary to follow Clean Code principles. In this article, we will break down the core concepts of clean code in TypeScript with clear examples. You will learn how to write code that will delight not only the compiler but also your colleagues.
Why is Clean Code important for TypeScript?
TypeScript adds static typing to JavaScript, which already helps avoid many errors. But types are just a tool. Without thoughtful architecture and a readable style, the code still turns into "spaghetti". Clean Code is a philosophy that makes code:
- Readable — any developer can quickly understand the logic.
- Maintainable — changes in one place don't break the entire application.
- Scalable — adding new features doesn't require rewriting half the project.
Applying these principles in TypeScript allows you to leverage the power of the type system to its fullest, creating self-documenting code.
Principle 1: Naming Variables and Functions
A good name is half the battle. The name should answer the questions "what?" and "why?", not "how?". Avoid single-letter variables and abbreviations that are only clear to you.
Bad example:
// What is d? And y?const d = new Date();const y = d.getFullYear();const fn = (a: number, b: number) => a + b;Good example:
const currentDate = new Date();const currentYear = currentDate.getFullYear();
const sum = (firstNumber: number, secondNumber: number): number => { return firstNumber + secondNumber;};Tip: Use meaningful names. For boolean values, use prefixes like is, has, should. For example: isActive, hasPermission.
Principle 2: Functions Should Be Small and Do One Thing
A function should not be responsible for several tasks at once. If a function does more than one thing, it's time to break it up. This improves reusability and simplifies testing.
Bad example (function does everything at once):
function processUserData(user: any) { // Validation if (!user.email || !user.name) { throw new Error('Invalid user'); } // Formatting const formattedUser = { fullName: user.name.toUpperCase(), emailDomain: user.email.split('@')[1] }; // Saving to DB database.save(formattedUser); // Sending email emailService.sendWelcomeEmail(formattedUser);}Good example (separation of concerns):
function validateUser(user: User): void { if (!user.email || !user.name) { throw new Error('Invalid user data'); }}
function formatUser(user: User): FormattedUser { return { fullName: user.name.toUpperCase(), emailDomain: user.email.split('@')[1] };}
async function processUserData(user: User): Promise<void> { validateUser(user); const formattedUser = formatUser(user); await database.save(formattedUser); await emailService.sendWelcomeEmail(formattedUser);}Each function now has a single clear purpose, and they are easy to test individually.
Principle 3: Use Types and Interfaces for Self-Documentation
TypeScript provides powerful tools for describing data. Do not overuse the any type — it kills all the benefits of static typing. Define an interface