소프트웨어 개발은 끊임없는 개선과 배움의 과정입니다. 이 글에서는 C# 코드 예제와 함께 실무에서 바로 적용할 수 있는 10가지 개발 습관을 소개합니다.
커밋은 하나의 논리적 변경에 집중해야 합니다. 예를 들어, 사용자 인증 기능을 추가할 때:
// 첫 번째 커밋: 기본 사용자 모델 추가
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string Email { get; set; }
public string PasswordHash { get; set; }
}
// 두 번째 커밋: 패스워드 해싱 서비스 추가
public interface IPasswordHasher
{
string HashPassword(string password);
bool VerifyPassword(string password, string hash);
}
public class BCryptPasswordHasher : IPasswordHasher
{
public string HashPassword(string password)
{
return BCrypt.Net.BCrypt.HashPassword(password);
}
public bool VerifyPassword(string password, string hash)
{
return BCrypt.Net.BCrypt.Verify(password, hash);
}
}
// 세 번째 커밋: 사용자 서비스 구현
public class UserService
{
private readonly IPasswordHasher _passwordHasher;
public UserService(IPasswordHasher passwordHasher)
{
_passwordHasher = passwordHasher;
}
public async Task<User> RegisterUser(string username, string email, string password)
{
var user = new User
{
Username = username,
Email = email,
PasswordHash = _passwordHasher.HashPassword(password)
};
// DB 저장 로직...
return user;
}
}
기존 코드를 더 나은 구조로 개선하는 예제:
// 리팩토링 전: 모든 로직이 한 메서드에 있음
public class OrderProcessor
{
public void ProcessOrder(Order order)
{
// 재고 확인
if (order.Items.Any(item => !IsInStock(item)))
throw new OutOfStockException();
// 결제 처리
var payment = ProcessPayment(order.Total);
if (!payment.IsSuccessful)
throw new PaymentFailedException();
// 배송 처리
CreateShipment(order);
// 이메일 발송
SendConfirmationEmail(order);
}
}
// 리팩토링 후: 책임이 분리된 작은 클래스들
public class OrderProcessor
{
private readonly IStockValidator _stockValidator;
private readonly IPaymentProcessor _paymentProcessor;
private readonly IShipmentService _shipmentService;
private readonly IEmailService _emailService;
public OrderProcessor(
IStockValidator stockValidator,
IPaymentProcessor paymentProcessor,
IShipmentService shipmentService,
IEmailService emailService)
{
_stockValidator = stockValidator;
_paymentProcessor = paymentProcessor;
_shipmentService = shipmentService;
_emailService = emailService;
}
public async Task ProcessOrder(Order order)
{
await _stockValidator.ValidateStock(order);
await _paymentProcessor.ProcessPayment(order);
await _shipmentService.CreateShipment(order);
await _emailService.SendConfirmation(order);
}
}
피처 플래그를 사용한 안전한 배포 예제:
public class NewFeatureToggle
{
private readonly IFeatureManager _featureManager;
public NewFeatureToggle(IFeatureManager featureManager)
{
_featureManager = featureManager;
}
public async Task ProcessRequest(Request request)
{
if (await _featureManager.IsEnabledAsync("NewAlgorithm"))
{
// 새로운 처리 로직
await ProcessWithNewAlgorithm(request);
}
else
{
// 기존 로직
await ProcessWithOldAlgorithm(request);
}
}
}
ASP.NET Core의 기능을 활용하는 예제:
// 프레임워크의 DI 컨테이너 활용
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// 내장 lifecycle 관리 활용
services.AddScoped<IUserService, UserService>();
services.AddTransient<IPasswordHasher, BCryptPasswordHasher>();
services.AddSingleton<ICacheService, RedisCacheService>();
// 내장 미들웨어 활용
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer();
// 자동 모델 검증 활용
services.AddControllers()
.AddFluentValidation();
}
}
// 컨트롤러에서는 최소한의 코드만 작성
[ApiController]
[Route("[controller]")]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
public UsersController(IUserService userService)
{
_userService = userService;
}
[HttpPost]
public async Task<ActionResult<UserDto>> Create(CreateUserRequest request)
{
// 모델 검증은 프레임워크가 자동으로 처리
var user = await _userService.CreateUser(request);
return Ok(user);
}
}
모듈화된 구조의 예제:
// Notifications 모듈
public class NotificationModule
{
public interface INotificationService
{
Task SendAsync(Notification notification);
}
public class EmailNotificationService : INotificationService
{
public async Task SendAsync(Notification notification)
{
// 이메일 발송 로직
}
}
public class PushNotificationService : INotificationService
{
public async Task SendAsync(Notification notification)
{
// 푸시 알림 로직
}
}
}
// Payments 모듈
public class PaymentModule
{
public interface IPaymentGateway
{
Task<PaymentResult> ProcessPayment(Payment payment);
}
public class StripeGateway : IPaymentGateway
{
public async Task<PaymentResult> ProcessPayment(Payment payment)
{
// Stripe 결제 로직
}
}
}
테스트 주도 API 설계 예제:
// 먼저 테스트 작성
public class OrderServiceTests
{
[Fact]
public async Task CreateOrder_WithValidItems_ShouldSucceed()
{
// Arrange
var service = new OrderService(mockRepository.Object);
var orderRequest = new CreateOrderRequest
{
Items = new[] { new OrderItem { ProductId = 1, Quantity = 2 } }
};
// Act
var result = await service.CreateOrder(orderRequest);
// Assert
Assert.NotNull(result);
Assert.Equal(OrderStatus.Created, result.Status);
}
}
// 테스트를 만족하는 구현
public class OrderService
{
private readonly IOrderRepository _repository;
public OrderService(IOrderRepository repository)
{
_repository = repository;
}
public async Task<Order> CreateOrder(CreateOrderRequest request)
{
var order = new Order
{
Items = request.Items.Select(i => new OrderItem
{
ProductId = i.ProductId,
Quantity = i.Quantity
}).ToList(),
Status = OrderStatus.Created
};
return await _repository.Create(order);
}
}
중복 코드를 제거하는 예제:
// 중복된 코드
public class UserService
{
public async Task<User> GetAdminUser(int id)
{
var user = await _repository.GetById(id);
if (user == null) throw new NotFoundException();
if (!user.IsAdmin) throw new UnauthorizedException();
return user;
}
public async Task<User> GetManagerUser(int id)
{
var user = await _repository.GetById(id);
if (user == null) throw new NotFoundException();
if (!user.IsManager) throw new UnauthorizedException();
return user;
}
}
// 리팩토링 후
public class UserService
{
private async Task<User> GetUserByRole(int id, Func<User, bool> roleValidator)
{
var user = await _repository.GetById(id);
if (user == null) throw new NotFoundException();
if (!roleValidator(user)) throw new UnauthorizedException();
return user;
}
public Task<User> GetAdminUser(int id) =>
GetUserByRole(id, user => user.IsAdmin);
public Task<User> GetManagerUser(int id) =>
GetUserByRole(id, user => user.IsManager);
}
확장 가능한 디자인 예제:
// 초기 설계
public interface IOrderProcessor
{
Task ProcessOrder(Order order);
}
// 변화 수용: 새로운 요구사항 (주문 처리 전/후 훅 추가)
public interface IOrderProcessor
{
Task ProcessOrder(Order order);
Task<bool> CanProcess(Order order);
Task OnProcessed(Order order);
}
// 데코레이터 패턴으로 기능 확장
public class LoggingOrderProcessor : IOrderProcessor
{
private readonly IOrderProcessor _inner;
private readonly ILogger _logger;
public LoggingOrderProcessor(IOrderProcessor inner, ILogger logger)
{
_inner = inner;
_logger = logger;
}
public async Task ProcessOrder(Order order)
{
_logger.LogInformation($"Processing order {order.Id}");
await _inner.ProcessOrder(order);
_logger.LogInformation($"Processed order {order.Id}");
}
}
기술 부채 관리 예제:
// 1. 지금 당장 문제가 되는 부채
public class LegacyDatabase
{
// 심각한 성능 문제를 일으키는 코드
public async Task<IEnumerable<Order>> GetAllOrders()
{
var orders = new List<Order>();
// N+1 쿼리 문제
foreach (var id in await GetAllOrderIds())
{
orders.Add(await GetOrderById(id));
}
return orders;
}
}
// 즉시 수정이 필요한 리팩토링
public class OptimizedDatabase
{
public async Task<IEnumerable<Order>> GetAllOrders()
{
return await _context.Orders
.Include(o => o.Items)
.ToListAsync();
}
}
// 2. 잠재적인 문제
public class UserManager
{
// 확장성 문제가 있는 코드
public async Task<User> GetUser(int id)
{
// 캐싱이 없어 DB 부하가 높음
return await _context.Users.FindAsync(id);
}
}
// 개선된 버전
public class CachedUserManager
{
private readonly ICache _cache;
public async Task<User> GetUser(int id)
{
var cacheKey = $"user_{id}";
return await _cache.GetOrSetAsync(cacheKey,
() => _context.Users.FindAsync(id));
}
}
테스트 용이한 설계 예제:
// 테스트하기 어려운 설계
public class OrderService
{
public async Task ProcessOrder(Order order)
{
// 직접적인 정적 메서드 호출
var paymentResult = await PaymentGateway.Process(order.Total);
if (paymentResult.Success)
{
await EmailSender.Send(order.CustomerEmail, "주문 완료");
}
}
}
// 테스트 가능한 설계
public class OrderService
{
private readonly IPaymentGateway _paymentGateway;
private readonly IEmailSender _emailSender;
public OrderService(
IPaymentGateway paymentGateway,
IEmailSender emailSender)
{
_paymentGateway = paymentGateway;
_emailSender = emailSender;
}
public async Task ProcessOrder(Order order)
{
var paymentResult = await _paymentGateway.Process(order.Total);
if (paymentResult.Success)
{
await _emailSender.Send(order.CustomerEmail, "주문 완료");
}
}
}
// 테스트
참고 사이트
https://zarar.dev/good-software-development-habits/
Good software development habits
Note: This got and got some attention. This post is not advice, it's what's working for me. It's easy to pick up bad habits and hard to create good o...
zarar.dev
Visual Studio AI 템플릿으로 5분 만에 AI 챗봇 만들기 (0) | 2025.03.12 |
---|---|
c# string.Join 과 Aggregate 의 차이점 (0) | 2024.12.26 |
C# : 날짜 용도별 DateTimeLibrary 의 메소드 샘플 코드 (0) | 2024.11.19 |
C# : LINQ 를 활용한 차집합과 교집합 구하기 (Except, Intersect) (0) | 2024.05.10 |
ADO.NET 및 Entity Framework & dapper 의 DbType.AnsiString 조사하기 (4) | 2024.02.27 |