소프트웨어 개발은 끊임없는 개선과 배움의 과정입니다. 이 글에서는 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/
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 |
EPPlus : Please set the ExcelPackage.LicenseContext property 대처방안 (4) | 2024.02.21 |