실무에서 private를 기본으로 사용하는 이유, 처음 코드 리뷰를 받을 때 “왜 이 필드가 public이에요?”라는 피드백을 받고 당황했던 경험 있으신가요? 학교나 강의에서는 public으로 빠르게 돌아가는 예제를 많이 접하다 보니, 실제 현업에 들어와서 private가 기본값처럼 쓰이는 문화가 낯설게 느껴질 수 있습니다. 하지만 이건 단순한 코딩 스타일 취향이 아닙니다. 수십 년간 소프트웨어 공학이 쌓아온 설계 원칙과 수많은 실패에서 비롯된 규칙입니다. 이 글에서는 private를 기본으로 써야 하는 이유를 코드가 어떻게 망가지는지 직접 보여드리면서, 원칙부터 실전 적용법까지 낱낱이 설명합니다.
목차
- 접근 제어자란 무엇인가 – 기초 개념 정리
- private를 기본으로 써야 하는 핵심 원리
- private가 가져다주는 실질적인 장점
- 잘못된 접근 제어자 사용이 만드는 문제
- 실전 단계별 활용법 – private 설계 패턴
- 전문가 관점 – 언어별 비교와 추천 도구
1. 접근 제어자란 무엇인가 – 기초 개념 정리
접근 제어자(Access Modifier) 란 클래스, 필드, 메서드, 생성자에 붙여서 외부에서 얼마나 접근할 수 있는지를 제한하는 키워드입니다. Java를 기준으로 네 가지가 있습니다.
Java 접근 제어자 4가지
| 접근 제어자 | 같은 클래스 | 같은 패키지 | 하위 클래스 | 외부 전체 |
|---|---|---|---|---|
public | ✓ | ✓ | ✓ | ✓ |
protected | ✓ | ✓ | ✓ | ✗ |
default(없음) | ✓ | ✓ | ✗ | ✗ |
private | ✓ | ✗ | ✗ | ✗ |
public은 어디서든 접근 가능하고, private는 해당 클래스 내부에서만 접근할 수 있습니다. 표면적으로는 단순한 접근 범위의 차이지만, 이 선택이 코드 전체의 유지보수성과 안정성을 결정합니다.
접근 제어자가 없는 세계를 상상해보자
모든 멤버가 public인 세상을 상상해 보겠습니다. 회사의 모든 문서가 전 직원에게 공개되어 있고, 누구나 어떤 파일이든 수정할 수 있는 상황입니다. 처음에는 빠르게 공유되어 편리해 보이지만, 중요한 재무 문서가 실수로 수정되거나, 인사 정보가 외부로 유출되거나, 누가 어떤 파일을 바꿨는지 추적이 불가능해집니다. 소프트웨어에서 public 필드를 남발하는 것은 이와 똑같은 문제를 만들어 냅니다.
왜 private가 “기본값”이 되어야 하는가
소프트웨어 공학에서는 최소 권한 원칙(Principle of Least Privilege) 이라는 개념이 있습니다. 필요한 것만큼만 권한을 주고, 나머지는 막아두라는 원칙입니다. 이를 접근 제어자에 적용하면 “일단 private로 시작하고, 공개가 필요한 것만 신중하게 열어라”가 됩니다.
실무에서 private를 기본으로 사용하는 문화는 이 원칙을 코딩 습관으로 내재화한 결과입니다. 처음부터 public으로 열어두면 나중에 닫기가 매우 어렵지만, private로 시작하면 필요할 때 열기는 훨씬 쉽습니다. 변경의 방향이 비대칭적입니다.
2. private를 기본으로 써야 하는 핵심 원리
캡슐화(Encapsulation) – 데이터와 행동을 하나로 묶기
캡슐화는 객체지향 프로그래밍의 4대 특성 중 하나로, 데이터(필드)와 그 데이터를 다루는 행동(메서드)을 하나의 단위(클래스)로 묶고, 내부 구현을 외부로부터 숨기는 것을 의미합니다.
약의 캡슐을 생각해보세요. 캡슐 안에 든 실제 성분(내부 구현)이 무엇인지, 어떻게 만들어졌는지 우리는 알 필요가 없습니다. 캡슐을 삼키면(인터페이스 사용) 효과가 나타납니다(결과). 내부가 바뀌어도(제조 방식 변경) 우리가 복용하는 방식은 달라지지 않습니다. 이것이 캡슐화의 핵심입니다.
java
// 캡슐화 미적용 – 내부가 그대로 노출
public class BankAccount {
public String owner;
public double balance; // 잔액이 직접 노출
public String accountNumber;
}
// 어디서든 직접 수정 가능 – 위험!
BankAccount account = new BankAccount();
account.balance = -999999; // 음수 잔액? 아무도 막지 않음
account.accountNumber = ""; // 빈 계좌번호? 역시 아무도 막지 않음
java
// 캡슐화 적용 – 내부는 숨기고 제어된 방법만 노출
public class BankAccount {
private String owner;
private double balance; // 외부 직접 접근 불가
private String accountNumber;
public BankAccount(String owner, String accountNumber, double initialBalance) {
if (initialBalance < 0) throw new IllegalArgumentException("초기 잔액은 0 이상이어야 합니다.");
if (accountNumber == null || accountNumber.isBlank())
throw new IllegalArgumentException("계좌번호는 필수입니다.");
this.owner = owner;
this.accountNumber = accountNumber;
this.balance = initialBalance;
}
// 외부에 공개할 행동만 public으로 노출
public void deposit(double amount) {
if (amount <= 0) throw new IllegalArgumentException("입금액은 0보다 커야 합니다.");
this.balance += amount;
}
public void withdraw(double amount) {
if (amount <= 0) throw new IllegalArgumentException("출금액은 0보다 커야 합니다.");
if (amount > this.balance) throw new IllegalStateException("잔액이 부족합니다.");
this.balance -= amount;
}
public double getBalance() {
return balance; // 읽기만 허용, 직접 수정 불가
}
}
private가 된 순간, 잔액을 바꾸려면 반드시 deposit() 또는 withdraw()를 통해야 합니다. 이 메서드 안에서 음수 검사, 잔액 부족 검사 등의 불변 조건(Invariant) 을 항상 보장할 수 있습니다. 외부에서 직접 수정하는 것은 불가능합니다.
정보 은닉(Information Hiding) – 구현 세부사항을 숨기기
정보 은닉은 캡슐화와 자주 함께 언급되지만 미묘하게 다릅니다. 캡슐화가 데이터와 행동을 묶는 것이라면, 정보 은닉은 외부에 불필요한 구현 세부사항을 노출하지 않는 것에 초점을 맞춥니다.
java
public class UserService {
// 내부 구현에 사용하는 캐시 – 외부가 알 필요 없음
private final Map<Long, User> userCache = new HashMap<>();
// 내부에서만 쓰는 헬퍼 메서드 – 외부가 알 필요 없음
private User fetchFromDatabase(Long id) {
// DB 조회 로직
return new User(id, "example");
}
private void updateCache(User user) {
userCache.put(user.getId(), user);
}
// 외부에는 오직 이것만 공개
public User findUser(Long id) {
if (userCache.containsKey(id)) {
return userCache.get(id);
}
User user = fetchFromDatabase(id);
updateCache(user);
return user;
}
}
나중에 HashMap 캐시를 Redis로 교체하거나, fetchFromDatabase 로직을 완전히 바꾸더라도, findUser(Long id)라는 공개 인터페이스는 전혀 변하지 않습니다. 이것이 정보 은닉이 가져다주는 핵심 가치, 즉 내부 구현의 자유로운 변경입니다.
최소 공개 원칙 – 열기는 쉽고 닫기는 어렵다
private로 시작하는 것이 중요한 현실적 이유가 하나 더 있습니다. 한 번 public으로 공개된 API는 닫기가 매우 어렵습니다. 라이브러리나 팀 내 공유 모듈이라면 다른 개발자들이 이미 그 public 멤버에 의존하고 있을 가능성이 높습니다. 갑자기 private로 바꾸면 연쇄적으로 컴파일 오류가 발생하고 다른 팀의 코드가 깨집니다.
반면 private 멤버는 언제든지 수정하거나 삭제해도 외부에 영향이 없습니다. 변경의 두려움 없이 내부를 자유롭게 개선할 수 있습니다. “일단 public”은 돌이키기 어려운 약속이고, “일단 private”는 필요할 때 열 수 있는 유보입니다.
3. private가 가져다주는 실질적인 장점
불변 조건 보장 – 객체의 상태를 항상 유효하게
private 필드는 외부에서 직접 수정할 수 없으므로, 객체의 상태가 항상 유효한 범위 안에 있도록 보장할 수 있습니다. 이를 불변 조건(Invariant) 이라고 합니다.
java
public class Rectangle {
private double width;
private double height;
public Rectangle(double width, double height) {
validateDimension(width, "너비");
validateDimension(height, "높이");
this.width = width;
this.height = height;
}
public void resize(double newWidth, double newHeight) {
validateDimension(newWidth, "너비");
validateDimension(newHeight, "높이");
this.width = newWidth;
this.height = newHeight;
}
private void validateDimension(double value, String name) {
if (value <= 0) {
throw new IllegalArgumentException(name + "는 0보다 커야 합니다: " + value);
}
}
public double area() { return width * height; }
public double perimeter() { return 2 * (width + height); }
}
width와 height가 private이므로 직사각형의 변이 절대 0 이하가 될 수 없습니다. public이었다면 rect.width = -5; 한 줄로 이 보장이 무너집니다.
변경 영향 범위 최소화 – 리팩토링이 두렵지 않다
실무에서 코드를 변경할 때 가장 두려운 것은 “이걸 바꾸면 어디가 깨질지 모른다”는 불안감입니다. private 멤버는 해당 클래스 내부에서만 사용되므로, 변경의 영향 범위가 그 클래스 하나로 제한됩니다.
java
public class OrderCalculator {
// private이므로 내부 구현을 자유롭게 변경 가능
private double applyDiscount(double price, int quantity) {
// 기존: 단순 퍼센트 할인
// return price * 0.9;
// 변경: 수량 기반 구간 할인으로 교체 – 외부에 영향 없음
if (quantity >= 100) return price * 0.75;
if (quantity >= 50) return price * 0.85;
if (quantity >= 10) return price * 0.90;
return price;
}
public double calculateTotal(double unitPrice, int quantity) {
double discountedPrice = applyDiscount(unitPrice, quantity);
return discountedPrice * quantity;
}
}
applyDiscount가 public이었다면 외부 코드가 이 메서드를 직접 호출하고 있을 수 있어서 시그니처나 반환 방식을 바꾸기 어렵습니다. private이기 때문에 내부 로직을 완전히 갈아엎어도 calculateTotal의 결과만 동일하면 됩니다.
단위 테스트 용이성 향상
역설적으로 들릴 수 있지만, private 멤버가 잘 설계되어 있으면 단위 테스트가 더 쉬워집니다. 테스트는 private 내부 구현이 아닌 public 인터페이스를 통해 결과를 검증하면 됩니다. 내부 구현이 바뀌어도 테스트는 수정할 필요가 없습니다.
java
// 좋은 테스트 – public 인터페이스만 검증
@Test
void 100개_이상_주문시_25퍼센트_할인_적용() {
OrderCalculator calculator = new OrderCalculator();
double total = calculator.calculateTotal(1000.0, 100);
assertThat(total).isEqualTo(75000.0); // 1000 * 0.75 * 100
}
// 나쁜 테스트 – private 메서드를 직접 테스트하려는 시도
// (리플렉션을 써야 하고, 내부 변경마다 테스트도 수정해야 함)
private 메서드를 직접 테스트하고 싶은 충동이 생긴다면, 그것은 해당 메서드가 독립적인 책임을 가진 별도 클래스로 분리되어야 한다는 신호일 수 있습니다.
4. 잘못된 접근 제어자 사용이 만드는 문제
실무에서 private를 기본으로 사용하지 않았을 때 실제로 어떤 문제가 생기는지 구체적으로 살펴보겠습니다.
문제 1 – 예측 불가능한 상태 변이
java
// 나쁜 예 – public 필드를 가진 User 클래스
public class User {
public String name;
public String email;
public int loginCount;
public boolean isAdmin;
}
// 코드베이스 어딘가에서...
User user = userRepository.findById(1L);
user.isAdmin = true; // 누구든지 관리자로 만들 수 있음!
user.loginCount = -1; // 음수 로그인 횟수? 논리적으로 불가능하지만 막을 방법이 없음
user.email = null; // null 이메일로 이후 NPE 발생 가능
이런 코드가 여러 곳에 흩어지면, User 객체의 상태가 언제, 어디서, 누구에 의해 변경되었는지 추적이 불가능해집니다. 버그가 발생해도 원인을 찾기가 극도로 어려워집니다.
문제 2 – 수정 불가능한 공개 API의 족쇄
java
// 초기 설계 – 내부 구현을 그대로 public으로 노출
public class ProductService {
// 내부 구현 상세가 그대로 노출되어 버림
public List<Product> productList = new ArrayList<>(); // public 필드!
public Map<String, Product> productCache = new HashMap<>(); // public 필드!
public List<Product> getProductList() {
return productList; // 내부 리스트를 직접 반환
}
}
// 다른 개발자가 이미 이렇게 사용 중...
ProductService service = new ProductService();
service.productList.add(new Product("해킹된 상품")); // 직접 수정!
service.productCache.clear(); // 캐시를 외부에서 지움!
List<Product> list = service.getProductList();
list.remove(0); // 반환된 리스트를 수정 → 내부 상태가 바뀜!
나중에 ArrayList를 LinkedList로 바꾸거나, 캐시를 Redis로 전환하려 해도, 이미 외부 코드가 내부 구현에 직접 의존하고 있기 때문에 변경이 불가능합니다. 기술 부채가 쌓이고 시스템이 경직됩니다.
문제 3 – 의도치 않은 상속으로 인한 취약성
java
// 기반 클래스
public class Vehicle {
public int speed; // public이면 하위 클래스가 직접 수정
public String fuel;
public void accelerate(int amount) {
speed += amount; // 유효성 검사 없이 속도 증가
}
}
// 하위 클래스가 상위 클래스 내부를 직접 건드림
public class ElectricCar extends Vehicle {
public void turboBoost() {
speed += 1000; // 직접 필드 수정 – 상위 클래스의 불변 조건 우회
fuel = "없음"; // 상태를 직접 변조
}
}
speed가 private였다면 ElectricCar는 반드시 accelerate() 메서드를 통해야 하고, 그 메서드 안에서 최대 속도 제한, 연료 상태 확인 등의 검사가 강제됩니다. public 필드는 상속 계층 전체에서 불변 조건을 보장하기 어렵게 만듭니다.
문제 4 – 보안 취약점으로 이어질 수 있다
웹 애플리케이션에서 내부 필드가 의도치 않게 직렬화되어 외부로 노출되는 경우가 있습니다.
java
// 위험한 설계 – 민감 정보가 public 필드로 노출
public class UserResponse {
public String username;
public String email;
public String passwordHash; // public이면 JSON 응답에 그대로 포함될 수 있음!
public String internalRole; // 내부 권한 정보가 외부에 노출!
public String secretToken; // 인증 토큰이 응답에 포함!
}
Jackson 같은 직렬화 라이브러리는 기본적으로 public 필드를 JSON에 포함합니다. 민감한 정보가 포함된 public 필드는 의도치 않게 API 응답으로 유출될 수 있습니다. private로 선언하고 노출할 필드만 명시적으로 선택했다면 이런 문제를 원천 차단할 수 있습니다.
5. 실전 단계별 활용법 – private 설계 패턴
Step 1. 필드는 항상 private으로 시작
java
// 모든 필드를 private으로 선언하는 것을 기본 습관으로
public class Order {
private final Long id; // 변경 불가 – final도 함께
private final String customerId;
private OrderStatus status; // 상태 변경은 메서드를 통해서만
private List<OrderItem> items;
private LocalDateTime orderedAt;
private double totalAmount;
// 생성은 생성자 또는 정적 팩토리 메서드를 통해서만
public static Order create(String customerId, List<OrderItem> items) {
Order order = new Order();
order.id = generateId();
order.customerId = customerId;
order.items = new ArrayList<>(items); // 방어적 복사
order.status = OrderStatus.PENDING;
order.orderedAt = LocalDateTime.now();
order.totalAmount = calculateTotal(items);
return order;
}
// 상태 변경은 명확한 의도를 가진 메서드로만
public void confirm() {
if (this.status != OrderStatus.PENDING) {
throw new IllegalStateException("대기 중인 주문만 확정할 수 있습니다.");
}
this.status = OrderStatus.CONFIRMED;
}
public void cancel(String reason) {
if (this.status == OrderStatus.DELIVERED) {
throw new IllegalStateException("배송 완료된 주문은 취소할 수 없습니다.");
}
this.status = OrderStatus.CANCELLED;
}
}
Step 2. 메서드는 필요한 것만 public으로 열기
java
public class EmailValidator {
// 외부에 공개할 메서드 – 하나만
public boolean isValid(String email) {
return isNotEmpty(email)
&& hasAtSign(email)
&& hasDomain(email)
&& isReasonableLength(email);
}
// 내부 구현 세부사항 – 모두 private
private boolean isNotEmpty(String email) {
return email != null && !email.isBlank();
}
private boolean hasAtSign(String email) {
return email.contains("@") && email.indexOf("@") == email.lastIndexOf("@");
}
private boolean hasDomain(String email) {
String[] parts = email.split("@");
return parts.length == 2 && parts[1].contains(".") && !parts[1].startsWith(".");
}
private boolean isReasonableLength(String email) {
return email.length() <= 254; // RFC 5321 표준
}
}
외부는 isValid(email) 하나만 알면 됩니다. 내부 검증 로직이 어떻게 구성되는지는 완전히 숨겨져 있습니다. 나중에 정규식 기반으로 검증 방식을 바꿔도 외부 코드는 전혀 수정할 필요가 없습니다.
Step 3. getter는 신중하게, setter는 더욱 신중하게
java
public class Product {
private Long id;
private String name;
private double price;
private int stockQuantity;
// getter는 필요한 것만 – 모든 필드에 자동 생성하지 않기
public Long getId() { return id; }
public String getName() { return name; }
public double getPrice() { return price; }
// setter 대신 의도가 명확한 메서드 사용
// BAD: public void setPrice(double price) { this.price = price; }
// GOOD:
public void adjustPrice(double newPrice) {
if (newPrice < 0) throw new IllegalArgumentException("가격은 음수일 수 없습니다.");
if (newPrice > this.price * 3) throw new IllegalArgumentException("한 번에 3배 이상 인상 불가.");
this.price = newPrice;
}
// 재고도 setter 대신 의미 있는 메서드로
public void increaseStock(int quantity) {
if (quantity <= 0) throw new IllegalArgumentException("증가량은 양수여야 합니다.");
this.stockQuantity += quantity;
}
public void decreaseStock(int quantity) {
if (quantity <= 0) throw new IllegalArgumentException("감소량은 양수여야 합니다.");
if (quantity > this.stockQuantity) throw new IllegalStateException("재고가 부족합니다.");
this.stockQuantity -= quantity;
}
// stockQuantity는 getter도 신중히 – 비즈니스 의미로 표현
public boolean isInStock() { return stockQuantity > 0; }
public boolean hasEnoughStock(int required) { return stockQuantity >= required; }
}
setPrice(double price) 대신 adjustPrice(double newPrice)를 쓰면, 단순한 값 변경이 아니라 비즈니스 규칙이 적용된 가격 조정임이 코드에서 명확하게 드러납니다.
Step 4. 컬렉션 반환 시 방어적 복사
java
public class Team {
private List<String> members = new ArrayList<>();
public void addMember(String member) {
if (member == null || member.isBlank()) {
throw new IllegalArgumentException("멤버 이름은 필수입니다.");
}
members.add(member);
}
// BAD: 내부 리스트를 그대로 반환 – 외부에서 수정 가능
// public List<String> getMembers() { return members; }
// GOOD: 방어적 복사 또는 불변 뷰 반환
public List<String> getMembers() {
return Collections.unmodifiableList(members); // 수정 시도 시 예외 발생
// 또는: return new ArrayList<>(members); // 복사본 반환
}
public int getMemberCount() { return members.size(); }
}
6. 전문가 관점 – 언어별 비교와 추천 도구
다른 언어에서의 private 철학
private를 기본으로 사용하는 철학은 Java만의 이야기가 아닙니다. 언어마다 표현 방식은 다르지만 핵심 철학은 동일합니다.
Python
Python은 접근 제어자 키워드가 없습니다. 대신 관례(Convention)로 접근을 제한합니다.
python
class BankAccount:
def __init__(self, balance):
self._balance = balance # 언더스코어 1개: "건드리지 마세요" 관례
self.__secret = "token" # 언더스코어 2개: 네임 맹글링으로 외부 접근 어렵게
def deposit(self, amount):
if amount <= 0:
raise ValueError("입금액은 양수여야 합니다.")
self._balance += amount
Kotlin
Kotlin은 기본 가시성이 public이지만, 실무에서는 private와 internal을 적극 활용합니다.
kotlin
class UserService(
private val userRepository: UserRepository, // 생성자 파라미터도 private
private val emailService: EmailService
) {
// 외부에는 이것만 공개
fun findUser(id: Long): User = userRepository.findById(id)
?: throw NoSuchElementException("사용자를 찾을 수 없습니다.")
// 내부 헬퍼는 private
private fun validateUserId(id: Long) {
require(id > 0) { "ID는 양수여야 합니다." }
}
}
TypeScript
typescript
class PaymentProcessor {
private readonly apiKey: string; // readonly로 불변성까지 보장
private transactionHistory: Transaction[] = [];
constructor(apiKey: string) {
this.apiKey = apiKey;
}
public processPayment(amount: number): boolean {
const result = this.callPaymentApi(amount);
this.recordTransaction(amount, result);
return result;
}
private callPaymentApi(amount: number): boolean {
// 외부로 노출하지 않는 내부 구현
return true;
}
private recordTransaction(amount: number, success: boolean): void {
this.transactionHistory.push({ amount, success, timestamp: new Date() });
}
}
SOLID 원칙과 private의 연결
실무에서 private를 기본으로 사용하는 습관은 SOLID 원칙과 긴밀하게 연결됩니다.
- SRP(단일 책임 원칙):
private메서드로 내부 로직을 분리하면 각 메서드가 하나의 명확한 책임을 갖게 됩니다. - OCP(개방-폐쇄 원칙): 내부 구현을
private으로 숨기면 외부 계약을 유지하면서 내부를 자유롭게 변경할 수 있습니다. - LSP(리스코프 치환 원칙): 상위 클래스의
private필드를 하위 클래스가 직접 수정할 수 없으므로 상위 클래스의 불변 조건이 보호됩니다. - ISP(인터페이스 분리 원칙):
public으로 노출하는 메서드를 최소화하면 자연스럽게 클라이언트가 사용하지 않는 메서드에 의존하지 않게 됩니다. - DIP(의존성 역전 원칙): 인터페이스만
public으로 열고 구현은private로 숨기면 추상화에 의존하는 설계가 자연스럽게 완성됩니다.
추천 도구 및 리소스
| 도구 / 리소스 | 용도 |
|---|---|
| IntelliJ IDEA | public 필드·메서드 경고, 접근 제어자 최적화 제안 |
| Checkstyle | 팀 전체에 private 우선 규칙 자동 적용 |
| SonarQube / SonarLint | 접근 제어자 관련 코드 품질 이슈 자동 감지 |
| 클린 코드 (로버트 C. 마틴) | 캡슐화·정보 은닉 실무 관점 설명 |
| 이펙티브 자바 (조슈아 블로크) | 아이템 15: “클래스와 멤버의 접근성을 최소화하라” |
| 객체지향의 사실과 오해 (조영호) | 캡슐화와 메시지 중심 설계 한국어 최고의 입문서 |
면접 대비 핵심 답변
“왜 필드를 private으로 선언하나요?” 외부에서 직접 값을 변경하지 못하도록 막아 객체의 불변 조건을 보장하고, 내부 구현이 바뀌어도 외부 코드에 영향을 주지 않기 위해서입니다. 캡슐화와 정보 은닉을 실현하는 기본 수단입니다.
“getter/setter를 무조건 만드는 것은 좋은 설계인가요?” 아닙니다. 모든 필드에 getter/setter를 만드는 것은 private를 선언해 놓고 실질적으로 public으로 만드는 것과 다름없습니다. 필요한 getter만 선택적으로 제공하고, setter 대신 비즈니스 의미를 담은 메서드(deposit, confirm, cancel 등)를 사용하는 것이 올바른 객체지향 설계입니다.
“private 메서드를 테스트하려면 어떻게 하나요?” private 메서드는 직접 테스트하지 않는 것이 원칙입니다. public메서드를 통해 간접적으로 검증합니다. 만약 private 메서드를 직접 테스트하고 싶은 충동이 강하게 든다면, 그 메서드가 별도의 클래스로 분리되어야 한다는 설계 신호로 해석하는 것이 좋습니다.
결론
실무에서 private를 기본으로 사용하는 이유는 단순한 관습이 아닙니다. 캡슐화로 객체의 불변 조건을 보장하고, 정보 은닉으로 내부 구현을 자유롭게 변경할 수 있으며, 최소 권한 원칙으로 변경의 영향 범위를 통제하기 위한 수십 년간 검증된 설계 원칙입니다. 실무에서 private를 기본으로 사용하는 습관 하나가 리팩토링의 두려움을 없애고, 버그의 원인을 좁히며, 팀 전체의 코드 품질을 높입니다. 오늘부터 새로운 클래스를 만들 때 “이 멤버를 반드시 외부에 공개해야 하는가?”를 먼저 질문하고, 기본값은 private로 시작하는 습관을 들여보세요. 코드가 달라지는 경험을 반드시 하게 됩니다.
답글 남기기
댓글을 달기 위해서는 로그인해야합니다.