Skip to content
~/sph.sh

JavaScript'te SOLID Prensipleri: TypeScript ve React ile Pratik Rehber

SOLID prensiplerininin modern JavaScript geliştirmede nasıl uygulanacağını öğrenin. TypeScript, React hooks ve fonksiyonel pattern'ler ile pratik örnekler - ayrıca ne zaman kullanmalı, ne zaman gereksiz.

SOLID prensipleri object-oriented programming için formüle edilmişti, ama modern JavaScript geliştirme farklı görünüyor - functional pattern'ler, React hooks, dynamic typing. Bu prensipler hala önemli mi? Cevap evet, ama önemli adaptasyonlarla.

Bu prensipler JavaScript'te değerli olmaya devam ediyor, ama çeviri gerekiyor. Sorun bunları kullanıp kullanmamak değil, inheritance yerine composition'ı ve katı interface'ler yerine duck typing'i tercih eden bir dilde nasıl uygulayacağımız.

Özet

Bu yazı SOLID'in beş prensibinin - Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation ve Dependency Inversion - JavaScript ve TypeScript geliştirmede nasıl adapte olduğunu inceliyor. React, Node.js ve TypeScript ile pratik örnekler üzerinden, bu prensiplerin kod kalitesini ne zaman geliştirdiğini ve ne zaman over-engineering'e yol açtığını keşfedeceğiz. Her prensip çalışan kod örnekleri, kaçınılması gereken anti-pattern'ler ve JavaScript'in fonksiyonel ve dinamik doğası için spesifik rehberlik içeriyor.

JavaScript Context'inde SOLID'i Anlamak

Zorluk

SOLID prensipleri Java ve C# gibi statik tipli, class tabanlı diller için tasarlandı. JavaScript farklı özellikler getiriyor:

  • Dynamic typing - TypeScript olmadan compile-time type checking yok
  • Functional pattern'ler - Function'lar first-class citizen
  • Composition odaklı - Inheritance'a daha az vurgu
  • Duck typing - Objeler explicit interface'lerle değil, davranışla validate ediliyor
  • React pattern'leri - Hook'lar ve component'ler farklı kısıtlamalara sahip

Soru SOLID'in JavaScript'e uygulanıp uygulanmayacağı değil, bu prensipleri nasıl etkili bir şekilde çevireceğimiz.

Single Responsibility Principle (SRP)

Tanım: Bir modülün değişmek için tek bir nedeni olmalı.

Bu en doğrudan JavaScript'e çevrilir. Class, function veya React component yazıyor olun, her biri bir concern ile ilgilenmelidir.

React Component Örneği

İşte yaygın bir ihlal - birden fazla sorumluluğu olan component:

typescript
// ❌ SRP'yi ihlal ediyor: Component data fetching, state ve rendering yapıyorfunction UserProfile({ userId }: { userId: string }) {  const [user, setUser] = useState(null);  const [loading, setLoading] = useState(false);  const [error, setError] = useState(null);
  useEffect(() => {    setLoading(true);    fetch(`/api/users/${userId}`)      .then(res => res.json())      .then(data => {        setUser(data);        setLoading(false);      })      .catch(err => {        setError(err);        setLoading(false);      });  }, [userId]);
  if (loading) return <Spinner />;  if (error) return <ErrorMessage error={error} />;  if (!user) return null;
  return (    <div>      <h1>{user.name}</h1>      <p>{user.email}</p>      <img src={user.avatar} alt={user.name} />    </div>  );}

Bu component'in değişmek için üç nedeni var: data fetching logic, state management veya rendering gereksinimleri. Her concern'i ayır:

typescript
// ✅ SRP'yi takip ediyor: Data fetching sorumluluğufunction useUser(userId: string) {  const [user, setUser] = useState(null);  const [loading, setLoading] = useState(false);  const [error, setError] = useState(null);
  useEffect(() => {    setLoading(true);    fetch(`/api/users/${userId}`)      .then(res => res.json())      .then(setUser)      .catch(setError)      .finally(() => setLoading(false));  }, [userId]);
  return { user, loading, error };}
// ✅ Presentation sorumluluğufunction UserProfile({ userId }: { userId: string }) {  const { user, loading, error } = useUser(userId);
  if (loading) return <Spinner />;  if (error) return <ErrorMessage error={error} />;  if (!user) return null;
  return <UserCard user={user} />;}
// ✅ Rendering sorumluluğufunction UserCard({ user }: { user: User }) {  return (    <div>      <h1>{user.name}</h1>      <p>{user.email}</p>      <img src={user.avatar} alt={user.name} />    </div>  );}

Şimdi her parçanın tek sorumluluğu var. useUser hook'u bağımsız test edilebilir ve diğer component'lerde yeniden kullanılabilir. UserCard user data'sını nereden geldiğini bilmeden render ediyor. API'deki değişiklikler sadece useUser'ı etkiliyor, presentation logic'i değil.

Yaygın SRP Anti-pattern'i: God Object'ler

typescript
// ❌ İlgisiz concern'leri olan modül// utils/user.jsexport function validateEmail(email) { /* ... */ }export function formatCurrency(amount) { /* ... */ }export function fetchWeatherData(city) { /* ... */ }export function compressImage(file) { /* ... */ }

Bu function'ların ortak hiçbir yanı yok. Bunları odaklanmış modüllere böl:

typescript
// ✅ Cohesive modüller// utils/validation.jsexport function validateEmail(email) { /* ... */ }export function validatePassword(password) { /* ... */ }
// utils/formatting.jsexport function formatCurrency(amount) { /* ... */ }export function formatDate(date) { /* ... */ }
// services/weather.jsexport function fetchWeatherData(city) { /* ... */ }

Open/Closed Principle (OCP)

Tanım: Software entity'leri extension'a açık, modification'a kapalı olmalı.

JavaScript'te bu, mevcut kodu değiştirmeden yeni functionality ekleme demek. Strategy pattern burada iyi çalışıyor.

Payment Processing Örneği

İşte ihlal - yeni payment method'ları eklemek mevcut kodu değiştirmeyi gerektiriyor:

typescript
// ❌ OCP'yi ihlal ediyor: Her yeni payment method için function'ı değiştirmelifunction processPayment(amount: number, method: string) {  if (method === 'credit-card') {    validateCreditCard();    chargeCreditCard(amount);  } else if (method === 'paypal') {    validatePayPalAccount();    chargePayPal(amount);  } else if (method === 'crypto') {    validateWallet();    transferCrypto(amount);  }  // Yeni method eklemek bu function'ı değiştirmeyi gerektiriyor}

Strategy pattern kullanarak functional yaklaşım:

typescript
// ✅ OCP'yi takip ediyor: Modification olmadan extend ediliyortype PaymentProcessor = {  validate: () => Promise<boolean>;  charge: (amount: number) => Promise<PaymentResult>;};
const createCreditCardProcessor = (cardDetails: CardDetails): PaymentProcessor => ({  validate: () => validateCreditCard(cardDetails),  charge: (amount) => chargeCreditCard(cardDetails, amount),});
const createPayPalProcessor = (email: string): PaymentProcessor => ({  validate: () => validatePayPalAccount(email),  charge: (amount) => chargePayPal(email, amount),});
const processPayment = async (  processor: PaymentProcessor,  amount: number): Promise<PaymentResult> => {  const isValid = await processor.validate();  if (!isValid) throw new Error('Payment validation failed');  return processor.charge(amount);};
// Kullanım - yeni processor eklemek processPayment'ta değişiklik gerektirmiyorawait processPayment(createCreditCardProcessor(cardDetails), 100);await processPayment(createPayPalProcessor('[email protected]'), 100);
// Mevcut kodu değiştirmeden crypto ekleconst createCryptoProcessor = (wallet: string): PaymentProcessor => ({  validate: () => validateWallet(wallet),  charge: (amount) => transferCrypto(wallet, amount),});
await processPayment(createCryptoProcessor(walletAddress), 100);

Bu TypeScript'in structural typing'i ile çalışıyor. Yeni processor'lar sadece shape'e uymalı - JavaScript'te explicit interface declaration gerekmiyor, TypeScript compile-time safety'de yardımcı oluyor.

Higher-Order Function'lar ile OCP

typescript
// ✅ Composition ile OCPtype Middleware = (data: any) => any;
const compose = (...fns: Middleware[]) => (data: any) =>  fns.reduce((result, fn) => fn(result), data);
// Base transformation'larconst toLowerCase = (str: string) => str.toLowerCase();const trim = (str: string) => str.trim();const removeSpaces = (str: string) => str.replace(/\s/g, '');
// Composing ile extend et - modification gerektirmezconst normalizeEmail = compose(trim, toLowerCase);const normalizeUsername = compose(trim, toLowerCase, removeSpaces);
// Yeni transformation ekleconst removeDashes = (str: string) => str.replace(/-/g, '');const normalizePhoneNumber = compose(trim, removeSpaces, removeDashes);

Liskov Substitution Principle (LSP)

Tanım: Object'ler program'ı bozmadan subtype'ları ile değiştirilebilir olmalı.

LSP ihlalleri inheritance ile yaygın. Klasik örnek:

typescript
// ❌ LSP'yi ihlal ediyor: Square, Rectangle'ın contract'ını bozuyorclass Rectangle {  constructor(    protected width: number,    protected height: number  ) {}
  setWidth(width: number) {    this.width = width;  }
  setHeight(height: number) {    this.height = height;  }
  getArea(): number {    return this.width * this.height;  }}
class Square extends Rectangle {  setWidth(width: number) {    this.width = width;    this.height = width; // Beklenmeyen davranış  }
  setHeight(height: number) {    this.width = height;    this.height = height; // Beklenmeyen davranış  }}
// Bu Square ile bozuluyorfunction resizeRectangle(rectangle: Rectangle) {  rectangle.setWidth(10);  rectangle.setHeight(5);  console.assert(rectangle.getArea() === 50); // Square ile başarısız (area = 25)}

Çözüm inheritance yerine composition:

typescript
// ✅ LSP'yi takip ediyor: Composition kullaninterface Shape {  getArea(): number;}
class Rectangle implements Shape {  constructor(    private width: number,    private height: number  ) {}
  setWidth(width: number) {    this.width = width;  }
  setHeight(height: number) {    this.height = height;  }
  getArea(): number {    return this.width * this.height;  }}
class Square implements Shape {  constructor(private size: number) {}
  setSize(size: number) {    this.size = size;  }
  getArea(): number {    return this.size * this.size;  }}
// Function'lar Shape interface ile çalışıyor - substitution sorunu yokfunction printArea(shape: Shape) {  console.log(`Area: ${shape.getArea()}`);}
printArea(new Rectangle(10, 5)); // ✅ ÇalışıyorprintArea(new Square(5)); // ✅ Çalışıyor

React Component'lerinde LSP

typescript
// ❌ LSP'yi ihlal ediyor: Enhanced button ek prop gerektiriyorinterface ButtonProps {  onClick: () => void;  label: string;}
function IconButton({ onClick, label, icon }: ButtonProps & { icon: string }) {  if (!icon) {    throw new Error('IconButton requires icon prop'); // ❌ Substitution'ı bozuyor  }  return (    <button onClick={onClick}>      <Icon name={icon} />      {label}    </button>  );}
// ✅ LSP'yi takip ediyor: Optional enhancementinterface IconButtonProps extends ButtonProps {  icon?: string; // Optional - substitution'ı bozmuyor}
function IconButton({ onClick, label, icon }: IconButtonProps) {  return (    <button onClick={onClick}>      {icon && <Icon name={icon} />}      {label}    </button>  );}
// Button kullanılan her yerde IconButton kullanılabilirfunction Form() {  const handleSubmit = () => console.log('Submitted');
  return (    <>      <Button onClick={handleSubmit} label="Submit" />      <IconButton onClick={handleSubmit} label="Submit" icon="check" />    </>  );}

Interface Segregation Principle (ISP)

Tanım: Client'lar kullanmadıkları interface'lere depend etmemeli.

JavaScript'in duck typing'i bunu doğal olarak destekliyor, ama TypeScript interface'leri ve React prop'ları explicit segregation'dan faydalanıyor.

Fat Interface Problemi

typescript
// ❌ ISP'yi ihlal ediyor: Fat interface kullanılmayan method implementasyonlarını zorluyorinterface Worker {  work(): void;  eat(): void;  sleep(): void;  getMaintenance(): void;}
class HumanWorker implements Worker {  work() { console.log('Working...'); }  eat() { console.log('Eating lunch...'); }  sleep() { console.log('Sleeping...'); }  getMaintenance() {    throw new Error('Humans do not need maintenance'); // ❌ Implement etmeye zorlanıyor  }}
class RobotWorker implements Worker {  work() { console.log('Working...'); }  getMaintenance() { console.log('Getting maintenance...'); }  eat() {    throw new Error('Robots do not eat'); // ❌ Implement etmeye zorlanıyor  }  sleep() {    throw new Error('Robots do not sleep'); // ❌ Implement etmeye zorlanıyor  }}

Odaklanmış interface'lere böl:

typescript
// ✅ ISP'yi takip ediyor: Segregated interface'lerinterface Workable {  work(): void;}
interface Eatable {  eat(): void;}
interface Sleepable {  sleep(): void;}
interface Maintainable {  getMaintenance(): void;}
// Sadece gerekli interface'leri implement etclass HumanWorker implements Workable, Eatable, Sleepable {  work() { console.log('Working...'); }  eat() { console.log('Eating lunch...'); }  sleep() { console.log('Sleeping...'); }}
class RobotWorker implements Workable, Maintainable {  work() { console.log('Working...'); }  getMaintenance() { console.log('Getting maintenance...'); }}
// Function'lar sadece ihtiyaç duydukları şeye depend ediyorfunction makeWork(worker: Workable) {  worker.work();}
function feedWorker(worker: Eatable) {  worker.eat();}
const human = new HumanWorker();const robot = new RobotWorker();
makeWork(human); // ✅ ÇalışıyormakeWork(robot); // ✅ ÇalışıyorfeedWorker(human); // ✅ Çalışıyor// feedWorker(robot); // ❌ Compile error - Robot Eatable implement etmiyor

React Component'lerinde ISP

typescript
// ❌ ISP'yi ihlal ediyor: Component tüm User type'a depend ediyorinterface User {  id: string;  name: string;  email: string;  address: Address;  phoneNumber: string;  preferences: UserPreferences;  billingInfo: BillingInfo;  // ... 20+ property daha}
function UserGreeting({ user }: { user: User }) {  return <h1>Hello, {user.name}!</h1>; // Sadece name kullanıyor}
// ✅ ISP'yi takip ediyor: Component sadece kullandığına depend ediyorinterface UserGreetingProps {  name: string;}
function UserGreeting({ name }: UserGreetingProps) {  return <h1>Hello, {name}!</h1>;}
// Kullanım: Sadece gerekli data'yı geçfunction App() {  const user: User = fetchUser();  return <UserGreeting name={user.name} />;}

ISP için TypeScript Utility Type'ları

typescript
// Büyük interfaceinterface User {  id: string;  email: string;  password: string;  firstName: string;  lastName: string;  address: Address;  phoneNumber: string;  createdAt: Date;}
// ✅ Utility type'lar kullanarak focused type'lar oluşturtype UserCredentials = Pick<User, 'email' | 'password'>;type UserProfile = Pick<User, 'id' | 'firstName' | 'lastName' | 'email'>;type UserContactInfo = Pick<User, 'email' | 'phoneNumber' | 'address'>;type PublicUser = Omit<User, 'password'>;
// Component'ler sadece gerekli property'leri alıyorfunction UserContactForm({ email, phoneNumber, address }: UserContactInfo) {  // Component logic}

Dependency Inversion Principle (DIP)

Tanım: High-level modüller low-level modüllere depend etmemeli. İkisi de abstraction'lara depend etmeli.

Bu testability ve flexibility için kritik.

Problem: Direct Dependency'ler

typescript
// ❌ DIP'yi ihlal ediyor: UserService doğrudan concrete implementation'lara depend ediyorclass MySQLDatabase {  connect() { /* ... */ }  query(sql: string) { /* ... */ }}
class EmailService {  sendEmail(to: string, subject: string, body: string) {    // Doğrudan Gmail SMTP kullanıyor  }}
class UserService {  private db = new MySQLDatabase(); // ❌ Tight coupling  private emailService = new EmailService(); // ❌ Tight coupling
  async createUser(userData: UserData) {    await this.db.connect();    const user = await this.db.query('INSERT INTO users...');    await this.emailService.sendEmail(user.email, 'Welcome', 'Welcome!');    return user;  }}
// Gerçek MySQL ve email service olmadan test edilemiyor// Implementation'ları değiştirilemiyor

Abstraction'lar ile constructor injection kullan:

typescript
// ✅ DIP'yi takip ediyor: Abstraction'lara depend etinterface Database {  connect(): Promise<void>;  query<T>(sql: string, params?: any[]): Promise<T>;}
interface EmailProvider {  send(to: string, subject: string, body: string): Promise<void>;}
// Concrete implementation'larclass MySQLDatabase implements Database {  async connect() { /* MySQL connection */ }  async query<T>(sql: string, params?: any[]): Promise<T> {    /* MySQL query */    return {} as T;  }}
class PostgreSQLDatabase implements Database {  async connect() { /* PostgreSQL connection */ }  async query<T>(sql: string, params?: any[]): Promise<T> {    /* PostgreSQL query */    return {} as T;  }}
// High-level modül abstraction'lara depend ediyorclass UserService {  constructor(    private db: Database,    private emailProvider: EmailProvider  ) {}
  async createUser(userData: UserData) {    await this.db.connect();    const user = await this.db.query<User>('INSERT INTO users...', [userData]);    await this.emailProvider.send(user.email, 'Welcome', 'Welcome!');    return user;  }}
// Production: Gerçek implementation'ları inject etconst userService = new UserService(  new MySQLDatabase(),  new GmailEmailProvider());
// Testing: Mock'ları inject etconst testUserService = new UserService(  new MockDatabase(),  new MockEmailProvider());
// Implementation'ları kolayca değiştirconst productionUserService = new UserService(  new PostgreSQLDatabase(),  new SendGridEmailProvider());

React'te Context API ile DIP

typescript
// ✅ React Context ile dependency injectioninterface ApiClient {  get<T>(url: string): Promise<T>;  post<T>(url: string, data: any): Promise<T>;}
interface Logger {  log(message: string): void;  error(message: string, error: Error): void;}
// Context'leri oluşturconst ApiClientContext = React.createContext<ApiClient | null>(null);const LoggerContext = React.createContext<Logger | null>(null);
// Dependency'ler için custom hook'larfunction useApiClient() {  const client = useContext(ApiClientContext);  if (!client) throw new Error('ApiClient not provided');  return client;}
function useLogger() {  const logger = useContext(LoggerContext);  if (!logger) throw new Error('Logger not provided');  return logger;}
// Component abstraction'lara depend ediyorfunction UserList() {  const apiClient = useApiClient();  const logger = useLogger();  const [users, setUsers] = useState<User[]>([]);
  useEffect(() => {    apiClient.get<User[]>('/users')      .then(setUsers)      .catch(error => logger.error('Failed to fetch users', error));  }, [apiClient, logger]);
  return (    <ul>      {users.map(user => <li key={user.id}>{user.name}</li>)}    </ul>  );}
// Root'ta dependency'leri provide etfunction App() {  const apiClient = useMemo(() => new FetchApiClient(), []);  const logger = useMemo(() => new ConsoleLogger(), []);
  return (    <ApiClientContext.Provider value={apiClient}>      <LoggerContext.Provider value={logger}>        <UserList />      </LoggerContext.Provider>    </ApiClientContext.Provider>  );}
// Testing: Mock'ları provide etfunction TestApp() {  const mockApiClient = useMemo(() => new MockApiClient(), []);  const mockLogger = useMemo(() => new MockLogger(), []);
  return (    <ApiClientContext.Provider value={mockApiClient}>      <LoggerContext.Provider value={mockLogger}>        <UserList />      </LoggerContext.Provider>    </ApiClientContext.Provider>  );}

SOLID Ne Zaman Overkill Oluyor

Tüm kod strict SOLID'den faydalanmıyor. Premature abstraction gereksiz complexity yaratıyor.

Overkill Örneği: Basit String Formatting

typescript
// ❌ Basit utility için overkillinterface StringFormatter {  format(input: string): string;}
class UpperCaseFormatter implements StringFormatter {  format(input: string): string {    return input.toUpperCase();  }}
class FormatterFactory {  static create(type: string): StringFormatter {    switch (type) {      case 'uppercase':        return new UpperCaseFormatter();      default:        throw new Error('Unknown formatter');    }  }}
// ✅ Basit function yeterliconst toUpperCase = (str: string) => str.toUpperCase();

SOLID'i Ne Zaman Strict Uygula

  • Büyük codebase'ler ile birden fazla takım
  • Kütüphaneler ve framework'ler public API'lar ile
  • Uzun ömürlü enterprise uygulamalar
  • Kompleks business logic yüksek testability gerektiren
  • Esneklik gerektiren sistemler implementation değişiminde

SOLID'i Ne Zaman Rahatlatmalı

  • Prototype'lar ve MVP'ler hızın architecture'dan önemli olduğu
  • Küçük utility function'lar abstraction overhead'inin faydaları aştığı
  • Stabil CRUD uygulamaları requirement değişikliklerinin düşük olduğu
  • One-off script'ler ve tool'lar kısa ömürlü
  • Pattern'ler henüz belirmediğinde - abstract etmeden önce duplication bekle

TypeScript'in Rolü

TypeScript JavaScript'te SOLID'i önemli ölçüde geliştiriyor:

typescript
// ISP ve DIP için explicit interface'lerinterface PaymentProcessor {  process(amount: number): Promise<void>;}
class CreditCardProcessor implements PaymentProcessor {  async process(amount: number): Promise<void> {    // Implement etmeli yoksa compile error  }}
// Type checking LSP ihlallerini önlüyorinterface Bird {  fly(): void;}
class Penguin implements Bird {  fly() {    throw new Error('Cannot fly'); // TypeScript bunu yakalıyor  }}
// Daha iyi: Farklı interface'lerinterface FlyingBird {  fly(): void;}
interface SwimmingBird {  swim(): void;}
class Sparrow implements FlyingBird {  fly() { console.log('Flying'); }}
class Penguin implements SwimmingBird {  swim() { console.log('Swimming'); }}
// Type-safe abstraction için generic'ler (DIP ve LSP)interface Repository<T> {  findById(id: string): Promise<T | null>;  save(entity: T): Promise<T>;  delete(id: string): Promise<void>;}
class UserRepository implements Repository<User> {  async findById(id: string): Promise<User | null> {    return null;  }
  async save(user: User): Promise<User> {    return user;  }
  async delete(id: string): Promise<void> {}}
// Class hierarchy'lere alternatif union type'lartype Shape =  | { type: 'circle'; radius: number }  | { type: 'rectangle'; width: number; height: number }  | { type: 'triangle'; base: number; height: number };
function calculateArea(shape: Shape): number {  switch (shape.type) {    case 'circle':      return Math.PI * shape.radius ** 2;    case 'rectangle':      return shape.width * shape.height;    case 'triangle':      return (shape.base * shape.height) / 2;  }}

Önemli Çıkarımlar

SOLID prensipleri JavaScript'te değerli olmaya devam ediyor ama adaptasyon gerektiriyor:

  1. SRP doğrudan çevrilir function'lara, modüllere, component'lere ve hook'lara
  2. OCP composition ile çalışır ve higher-order function'lar, derin inheritance değil
  3. LSP TypeScript ile daha önemli - dynamic typing duck typing'e izin veriyor
  4. ISP doğal olarak destekleniyor JavaScript'in esnekliği ile - TypeScript bunu explicit yapıyor
  5. DIP testability için essential - constructor injection ve Context API iyi çalışıyor

React hook'ları doğal olarak SOLID ile uyumlu:

  • Custom hook'lar SRP'yi enforce ediyor
  • Hook'lar modification olmadan compose oluyor (OCP)
  • Hook interface'leri minimal olmalı (ISP)
  • Dependency'ler parameter veya Context ile inject ediliyor (DIP)

Pragmatizm ile prensipleri dengele:

  • Küçük utility'ler abstraction layer'a ihtiyaç duymuyor
  • Abstract etmeden önce pattern'lerin belirmesini bekle
  • Prototype'lar hız için architecture'ı atlayabilir
  • Büyük codebase'ler strict SOLID'den faydalanıyor

TypeScript SOLID'i geliştiriyor:

  • Interface'ler ISP ve DIP'yi explicit yapıyor
  • Type checking LSP ihlallerini önlüyor
  • Generic'ler type-safe abstraction sağlıyor
  • Utility type'lar focused interface'ler oluşturmaya yardımcı oluyor

Hedef SOLID prensiplerine dogmatik bağlılık değil, kod kalitesini ne zaman geliştirdiklerini ve ne zaman gereksiz complexity getirdiklerini anlamak. JavaScript'in dinamik, fonksiyonel ortamında, bu prensipler dilin güçlü yönlerine düşünceli bir şekilde adapte edildiğinde değerli rehberlik sağlıyor.

İlgili Yazılar