Dart Best Practices: Temiz ve Sürdürülebilir Kod Rehberi

13 dakika okuma9 Şubat 2026Güncellendi: 9 Mar 2026
Dart best practicesDart clean codeDart null safetyDart naming conventionsFlutter Dart rehberiDart code qualityDart lint rulesmaintainable Dart code

# Dart Best Practices: Temiz ve Sürdürülebilir Kod Yazmak

Dart'ta kaliteyi artıran en önemli unsur, tutarlı kod standartlarıdır. Yıllar içinde geliştirdiğim bu pratikler; hata oranını düşürür, yeni geliştiricilerin projeye adaptasyonunu hızlandırır ve kod incelemelerini kolaylaştırır. Bu rehberde isimlendirmeden Dart 3+ özelliklerine kadar her şeyi ele alıyorum.

İsimlendirme Kuralları

İyi isimlendirme, yorum yazma ihtiyacının büyük bölümünü ortadan kaldırır. Dart stil rehberi belli bir sebepten katıdır.

Sınıflar, Enum'lar ve Typedef'ler

Tipler için `UpperCamelCase` kullanın. İsimler implementasyonu değil, alan modelini yansıtmalı.

dart
class=class="code-string">"code-comment">// Kötü
class Mgr {
  void doStuff() {}
}

class=class="code-string">"code-comment">// İyi
class AuthenticationManager {
  void signInWithEmail(String email, String password) {}
}

Değişkenler, Parametreler ve Fonksiyonlar

`lowerCamelCase` kullanın. Boolean değişkenler bir iddia gibi okunmalı.

dart
class=class="code-string">"code-comment">// Kötü
bool flag = true;
String s = class="code-string">'merhaba';
int process(int x) => x * class="code-number">2;

class=class="code-string">"code-comment">// İyi
bool isAuthenticated = true;
String welcomeMessage = class="code-string">'merhaba';
int doubleQuantity(int quantity) => quantity * class="code-number">2;

Sabitler ve Dosya İsimleri

Sabitler için `lowerCamelCase` kullanın (`BUYUK_HARFLI` stil değil). Dosya isimleri `snake_case` olmalı.

dart
class=class="code-string">"code-comment">// Kötü
const int MAX_RETRY_COUNT = class="code-number">3;
class=class="code-string">"code-comment">// dosya: AuthenticationManager.dart

class=class="code-string">"code-comment">// İyi
const int maxRetryCount = class="code-number">3;
class=class="code-string">"code-comment">// dosya: authentication_manager.dart

Private Üyeler

Alt çizgi ile başlatın. Public API yüzeyini mümkün olduğunca küçük tutun.

dart
class UserRepository {
  final ApiClient _apiClient;
  final Cache _cache;

  UserRepository(this._apiClient, this._cache);

  class=class="code-string">"code-comment">// Sadece tüketicilerin gerçekten ihtiyaç duyduğunu açığa çıkarın
  Future<User> fetchUser(String id) async {
    return _cache.get(id) ?? await _fetchAndCache(id);
  }

  Future<User> _fetchAndCache(String id) async {
    final user = await _apiClient.getUser(id);
    _cache.set(id, user);
    return user;
  }
}

Null Safety

Dart'ın sound null safety özelliği en büyük avantajlarından biridir. Ona karşı savaşmak yerine bilinçli kullanın.

Gereksiz Nullable Tiplerden Kaçının

Her nullable tip, gelecekteki sizin cevaplaması gereken bir soru işaretidir. Non-nullable yüzeyi mümkün olduğunca geniş tutun.

dart
class=class="code-string">"code-comment">// Kötü - neden nullable?
class UserProfile {
  String? name;
  String? email;
  int? age;

  UserProfile({this.name, this.email, this.age});
}

class=class="code-string">"code-comment">// İyi - zorunlu alanlar non-nullable
class UserProfile {
  final String name;
  final String email;
  final int age;

  const UserProfile({
    required this.name,
    required this.email,
    required this.age,
  });
}

`late` Kullanımında Dikkatli Olun

`late`, derleyiciye "bu değeri okunmadan önce kesinlikle atayacağım" sözü vermektir. Bu sözü tutmazsanız runtime hatası alırsınız ki bu null safety'nin amacını boşa çıkarır.

dart
class=class="code-string">"code-comment">// Tehlikeli - init öncesi erişimde runtime hatası
late final DatabaseConnection _db;

class=class="code-string">"code-comment">// Daha güvenli - nullable ile açık kontrol
DatabaseConnection? _db;

Future<DatabaseConnection> getDb() async {
  return _db ??= await DatabaseConnection.open();
}

Null-Aware Operatörler

Tam seti öğrenin: `??`, `??=`, `?.`, `?..` ve `!`.

dart
class=class="code-string">"code-comment">// Kötü - gereksiz uzun null kontrolleri
String displayName;
if (user.nickname != null) {
  displayName = user.nickname!;
} else {
  displayName = user.fullName;
}

class=class="code-string">"code-comment">// İyi - kısa null-aware zincir
final displayName = user.nickname ?? user.fullName;

class=class="code-string">"code-comment">// İyi - null-aware cascade
canvas?.drawRect(rect)
  ..fillColor = Colors.blue
  ..strokeWidth = class="code-number">2.0;

Immutable Veri Modelleri

Mutable state, şaşırtıcı sayıda hatanın kaynağıdır. Kendi projelerimde tüm veri modelleri için istisnasız immutability uyguluyorum.

dart
class=class="code-string">"code-comment">// Kötü - mutable, eşitlik yok
class Product {
  String name;
  double price;
  bool inStock;

  Product(this.name, this.price, this.inStock);
}

class=class="code-string">"code-comment">// İyi - immutable, değer eşitliği, kolay kopyalama
class Product {
  final String name;
  final double price;
  final bool inStock;

  const Product({
    required this.name,
    required this.price,
    required this.inStock,
  });

  Product copyWith({
    String? name,
    double? price,
    bool? inStock,
  }) {
    return Product(
      name: name ?? this.name,
      price: price ?? this.price,
      inStock: inStock ?? this.inStock,
    );
  }

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is Product &&
          name == other.name &&
          price == other.price &&
          inStock == other.inStock;

  @override
  int get hashCode => Object.hash(name, price, inStock);
}

Büyük projelerde bu tür boilerplate kodu otomatik üretmek için `freezed` paketini öneriyorum.

Hata Yönetimi

Exception'ları asla kontrol akışı için kullanmayın. Beklenen hataları açıkça modelleyin.

Özel Exception Hiyerarşisi

dart
class=class="code-string">"code-comment">// Kötü - string mesajlı genel exceptionclass="code-string">'lar
throw Exception('Kullanıcı bulunamadıclass="code-string">');
throw Exception('Ağ hatasıclass="code-string">');

class=class="code-string">"code-comment">// İyi - tipli exception hiyerarşisi
sealed class AppException implements Exception {
  final String message;
  final StackTrace? stackTrace;

  const AppException(this.message, [this.stackTrace]);
}

class NetworkException extends AppException {
  final int? statusCode;
  const NetworkException(super.message, {this.statusCode, super.stackTrace});
}

class ValidationException extends AppException {
  final Map<String, String> fieldErrors;
  const ValidationException(super.message, {required this.fieldErrors});
}

class NotFoundException extends AppException {
  final String resourceType;
  final String resourceId;
  const NotFoundException(this.resourceType, this.resourceId)
      : super('$resourceType (id: $resourceId) bulunamadı');
}

Result Tip Deseni

Beklenen şekillerde başarısız olabilecek işlemler için fırlatmak yerine bir sonuç döndürün.

dart
sealed class Result<T> {
  const Result();
}

class Success<T> extends Result<T> {
  final T value;
  const Success(this.value);
}

class Failure<T> extends Result<T> {
  final AppException error;
  const Failure(this.error);
}

class=class="code-string">"code-comment">// Kullanım
Future<Result<User>> fetchUser(String id) async {
  try {
    final response = await _api.get(class="code-string">'/users/$id');
    return Success(User.fromJson(response.data));
  } on DioException catch (e) {
    return Failure(NetworkException(
      class="code-string">'Kullanıcı bilgisi alınamadı',
      statusCode: e.response?.statusCode,
    ));
  }
}

class=class="code-string">"code-comment">// Sonucu tüketme
final result = await fetchUser(class="code-string">'class="code-number">123');
switch (result) {
  case Success(:final value):
    showProfile(value);
  case Failure(:final error):
    showError(error.message);
}

Extension Metodları

Extension'lar modellerinizi temiz tutarken domain'e özel kolaylıklar eklemenizi sağlar.

dart
extension StringValidation on String {
  bool get isValidEmail =>
      RegExp(rclass="code-string">'^[\w-\.]+@([\w-]+\.)+[\w-]{class="code-number">2,class="code-number">4}$').hasMatch(this);

  bool get isStrongPassword =>
      length >= class="code-number">8 &&
      contains(RegExp(rclass="code-string">'[A-Z]')) &&
      contains(RegExp(rclass="code-string">'[class="code-number">0-class="code-number">9]')) &&
      contains(RegExp(rclass="code-string">'[!@#$%^&*]'));

  String get capitalized =>
      isEmpty ? this : class="code-string">'${this[class="code-number">0].toUpperCase()}${substring(class="code-number">1)}';
}

extension DateTimeFormatting on DateTime {
  String get relativeTime {
    final diff = DateTime.now().difference(this);
    if (diff.inDays > class="code-number">365) return class="code-string">'${diff.inDays ~/ class="code-number">365}y önce';
    if (diff.inDays > class="code-number">30) return class="code-string">'${diff.inDays ~/ class="code-number">30}ay önce';
    if (diff.inDays > class="code-number">0) return class="code-string">'${diff.inDays}g önce';
    if (diff.inHours > class="code-number">0) return class="code-string">'${diff.inHours}sa önce';
    if (diff.inMinutes > class="code-number">0) return class="code-string">'${diff.inMinutes}dk önce';
    return class="code-string">'az önce';
  }
}

extension IterableExtensions<T> on Iterable<T> {
  T? firstWhereOrNull(bool Function(T) test) {
    for (final element in this) {
      if (test(element)) return element;
    }
    return null;
  }

  Map<K, List<T>> groupBy<K>(K Function(T) keyOf) {
    final map = <K, List<T>>{};
    for (final element in this) {
      (map[keyOf(element)] ??= []).add(element);
    }
    return map;
  }
}

Sealed Class'lar ve Pattern Matching

Dart 3 ile gelen sealed class ve exhaustive pattern matching, state yönetimi ve domain modelleme için devrim niteliğinde.

UI State Modelleme

dart
sealed class PageState<T> {
  const PageState();
}

class Loading<T> extends PageState<T> {
  const Loading();
}

class Loaded<T> extends PageState<T> {
  final T data;
  const Loaded(this.data);
}

class Error<T> extends PageState<T> {
  final String message;
  final VoidCallback? onRetry;
  const Error(this.message, {this.onRetry});
}

class=class="code-string">"code-comment">// Exhaustive switch - bir case'i atlarsanız derleyici uyarır
Widget buildPage(PageState<List<Product>> state) {
  return switch (state) {
    Loading() => const CircularProgressIndicator(),
    Loaded(:final data) => ProductList(products: data),
    Error(:final message, :final onRetry) => ErrorView(
        message: message,
        onRetry: onRetry,
      ),
  };
}

Domain Mantığı Modelleme

dart
sealed class PaymentMethod {
  const PaymentMethod();
}

class CreditCard extends PaymentMethod {
  final String last4;
  final String brand;
  const CreditCard({required this.last4, required this.brand});
}

class BankTransfer extends PaymentMethod {
  final String iban;
  const BankTransfer({required this.iban});
}

class DigitalWallet extends PaymentMethod {
  final String provider;
  const DigitalWallet({required this.provider});
}

String describePayment(PaymentMethod method) => switch (method) {
  CreditCard(:final brand, :final last4) => class="code-string">'$brand *$last4',
  BankTransfer(:final iban) => class="code-string">'Havale: $iban',
  DigitalWallet(:final provider) => class="code-string">'$provider ile öde',
};

Dart 3+ ile Gelen ve Kullanmanız Gereken Özellikler

Record'lar

Tam bir sınıf yazmadan, hafif ve immutable veri grupları oluşturun.

dart
class=class="code-string">"code-comment">// Dart class="code-number">3 öncesi - sınıf ya da List döndürmek gerekiyordu
class Pair<A, B> {
  final A first;
  final B second;
  Pair(this.first, this.second);
}

class=class="code-string">"code-comment">// Dart class="code-number">3 - recordclass="code-string">'lar
(String, int) parseNameAge(String input) {
  final parts = input.split(':class="code-string">');
  return (parts[class="code-number">0], int.parse(parts[class="code-number">1]));
}

class=class="code-string">"code-comment">// Adlandırılmış alanlarla daha okunabilir
({String host, int port}) parseAddress(String address) {
  final parts = address.split(':class="code-string">');
  return (host: parts[class="code-number">0], port: int.parse(parts[class="code-number">1]));
}

class=class="code-string">"code-comment">// Destructuring
final (name, age) = parseNameAge('Ali:class="code-number">30class="code-string">');
final (:host, :port) = parseAddress('localhost:class="code-number">8080');

If-Case ve Switch Expression

dart
class=class="code-string">"code-comment">// Satır içi pattern matching için if-case
void processResponse(Object response) {
  if (response case {class="code-string">'status': class="code-string">'ok', class="code-string">'data': Map data}) {
    handleData(data);
  }

  if (response case [int first, int second, ...]) {
    print(class="code-string">'İlk iki eleman: $first, $second');
  }
}

class=class="code-string">"code-comment">// Switch expression - değer döndürür
String httpStatus(int code) => switch (code) {
  class="code-number">200 => class="code-string">'OK',
  class="code-number">301 => class="code-string">'Kalıcı Yönlendirme',
  class="code-number">404 => class="code-string">'Bulunamadı',
  >= class="code-number">500 && < class="code-number">600 => class="code-string">'Sunucu Hatası',
  _ => class="code-string">'Bilinmeyen',
};

Sınıf Değiştiricileri

dart
class=class="code-string">"code-comment">// interface class - implement edilebilir ama extend edilemez
interface class Authenticator {
  Future<bool> authenticate(String token);
}

class=class="code-string">"code-comment">// base class - extend edilebilir ama implement edilemez
base class Repository {
  final Database _db;
  Repository(this._db);
}

class=class="code-string">"code-comment">// final class - kütüphane dışından ne extend ne implement edilebilir
final class AppConfig {
  final String apiUrl;
  final Duration timeout;
  const AppConfig({required this.apiUrl, required this.timeout});
}

class=class="code-string">"code-comment">// mixin class - hem mixin hem sınıf olarak kullanılabilir
mixin class Loggable {
  void log(String message) => print(class="code-string">'[${runtimeType}] $message');
}

Yaygın Dart Anti-pattern'leri

Anti-pattern: String Tabanlı Tip Kullanımı

dart
class=class="code-string">"code-comment">// Kötü - hatalar sadece runtimeclass="code-string">'da ortaya çıkar
void setTheme(String theme) {
  if (theme == 'darkclass="code-string">') { class=class="code-string">"code-comment">/* ... */ }
  else if (theme == 'lightclass="code-string">') { class=class="code-string">"code-comment">/* ... */ }
}
setTheme('drak'); class=class="code-string">"code-comment">// yazım hatası, derleyici uyarısı yok

class=class="code-string">"code-comment">// İyi - tip güvenli
enum AppTheme { dark, light, system }

void setTheme(AppTheme theme) => switch (theme) {
  AppTheme.dark => activateDarkMode(),
  AppTheme.light => activateLightMode(),
  AppTheme.system => followSystemTheme(),
};

Anti-pattern: Tanrı Sınıfları

dart
class=class="code-string">"code-comment">// Kötü - tek sınıf her şeyi yapıyor
class UserManager {
  Future<User> fetchUser() async { class=class="code-string">"code-comment">/* ... */ }
  void validateEmail(String email) { class=class="code-string">"code-comment">/* ... */ }
  void sendPushNotification(String message) { class=class="code-string">"code-comment">/* ... */ }
  void writeToAnalytics(String event) { class=class="code-string">"code-comment">/* ... */ }
  Widget buildProfileCard() { class=class="code-string">"code-comment">/* ... */ }
}

class=class="code-string">"code-comment">// İyi - tek sorumluluk
class UserRepository { class=class="code-string">"code-comment">/* fetch, cache */ }
class EmailValidator { class=class="code-string">"code-comment">/* doğrulama */ }
class NotificationService { class=class="code-string">"code-comment">/* push, yerel bildirim */ }
class AnalyticsTracker { class=class="code-string">"code-comment">/* olaylar */ }
class ProfileCard extends StatelessWidget { class=class="code-string">"code-comment">/* arayüz */ }

Anti-pattern: Her Şeyi Yakalamak

dart
class=class="code-string">"code-comment">// Kötü - tüm hataları sessizce yutar
try {
  await riskyOperation();
} catch (e) {
  print(e);
}

class=class="code-string">"code-comment">// İyi - belirli tipleri yakalayın, beklenmedikleri tekrar fırlatın
try {
  await riskyOperation();
} on NetworkException catch (e) {
  showSnackbar(class="code-string">'Bağlantı hatası: ${e.message}');
} on ValidationException catch (e) {
  showFieldErrors(e.fieldErrors);
} catch (e, stackTrace) {
  class=class="code-string">"code-comment">// Beklenmedik hata - logla ve tekrar fırlat
  crashReporter.record(e, stackTrace);
  rethrow;
}

Anti-pattern: İç İçe Future'lar

dart
class=class="code-string">"code-comment">// Kötü - callback piramidi
fetchUser(id).then((user) {
  fetchOrders(user.id).then((orders) {
    fetchDetails(orders.first.id).then((details) {
      updateUI(details);
    });
  });
});

class=class="code-string">"code-comment">// İyi - düz async/await
final user = await fetchUser(id);
final orders = await fetchOrders(user.id);
final details = await fetchDetails(orders.first.id);
updateUI(details);

Lint Yapılandırması

Kendi projelerimde bu kuralları ilk günden uyguluyorum. Katı bir `analysis_options.yaml` dosyası, sorunları kod incelemesine ulaşmadan yakalar.

yaml
include: package:flutter_lints/flutter.yaml

analyzer:
  errors:
    missing_return: error
    dead_code: warning
    unused_import: warning
  language:
    strict-casts: true
    strict-raw-types: true

linter:
  rules:
    # Stil
    - always_declare_return_types
    - annotate_overrides
    - avoid_print
    - prefer_const_constructors
    - prefer_const_declarations
    - prefer_final_fields
    - prefer_final_locals
    - sort_constructors_first

    # Güvenlik
    - avoid_dynamic_calls
    - avoid_returning_null_for_future
    - cancel_subscriptions
    - close_sinks
    - literal_only_boolean_expressions
    - no_adjacent_strings_in_list
    - throw_in_finally
    - unnecessary_statements
    - avoid_slow_async_io

    # Okunabilirlik
    - prefer_single_quotes
    - require_trailing_commas
    - use_named_constants
    - unnecessary_lambdas
    - prefer_expression_function_bodies

Ayrıca projenin tamamında basit lint sorunlarını otomatik düzeltmek için düzenli olarak `dart fix --apply` çalıştırmanızı öneriyorum.

Sonuç

Temiz Dart kodu, yeni geliştiricilerin adaptasyon süresini ve uzun vadeli bakım maliyetlerini ciddi oranda düşürür. Buradaki pratikler teorik idealler değil; her projemde uyguladığım kalıplardır. Katı linting ile başlayın, immutability'yi zorunlu kılın, domain modelleme için sealed class'ları benimseyin ve pattern matching'i her fırsatta kullanın. Gelecekteki siz ve ekip arkadaşlarınız bunun için size teşekkür edecek.

Ekip genelinde Dart kalite standartları belirlemek ve CI'da otomatik denetim kurmak için iletişime geçebilirsiniz.

İlgili Makaleler

Flutter Projeniz mi Var?

iOS, Android ve web için yüksek performanslı Flutter uygulamaları geliştiriyorum.

İletişime Geç