시니어인 척하는 주니어 & 주니어인 척하는 시니어 — 경력의 역설
시니어인 척하는 주니어 & 주니어인 척하는 시니어 — 경력의 역설
"연차는 실력의 필요조건이지 충분조건이 아님. 그런데 충분조건인 줄 아는 사람이 너무 많음."
시니어의 진짜 의미
"시니어 개발자"라는 타이틀은 아마 IT 업계에서 가장 오용되는 단어일 것임.
어떤 회사에서는 3년차면 시니어이고, 어떤 회사에서는 10년차여도 시니어가 아님. 어떤 회사에서는 시니어가 코딩만 하고, 어떤 회사에서는 시니어가 팀을 리드함.
시니어 개발자의 다양한 정의
회사 A (스타트업): 3년차 이상 + 혼자 기능 개발 가능 = 시니어 회사 B (대기업): 7년차 이상 + 기술 리드 경험 = 시니어 회사 C (FAANG): 연차 무관 + 조직에 대한 영향력 = 시니어 (L5/L6) 회사 D (SI): 5년 지나면 자동으로 시니어 (시간이 해결)
이런 혼란 속에서 "시니어인 척"과 "주니어인 척"이 탄생함.
시니어의 본질: 영향력
// 시니어 = 연차가 아니라 영향력
interface DeveloperLevel {
title: string;
impactScope: string;
keySkill: string;
}
const levels: DeveloperLevel[] = [
{
title: "주니어",
impactScope: "자기 태스크",
keySkill: "주어진 문제를 해결할 수 있음",
},
{
title: "미드레벨",
impactScope: "팀 내 프로젝트",
keySkill: "문제를 스스로 정의하고 해결할 수 있음",
},
{
title: "시니어",
impactScope: "팀 전체",
keySkill: "팀의 기술적 방향을 설정하고 다른 사람을 성장시킴",
},
{
title: "스태프+",
impactScope: "조직 전체",
keySkill: "여러 팀에 걸친 기술적 전략을 수립하고 실행",
},
];
// 핵심: 시니어는 "더 어려운 코딩"이 아니라 "더 넓은 영향력"
// 코딩을 더 잘하는 건 미드레벨까지의 성장이고
// 시니어부터는 "팀을 어떻게 더 잘 만드느냐"가 핵심
시니어인 척하는 주니어의 패턴
2-3년차 개발자 중 일부가 보이는 "시니어 코스프레" 패턴들. 본인은 시니어라고 생각하지만 주변에서 보기에는... 글쎄.
패턴 1: 용어 폭격기
// "우리 이거 도메인 주도 설계로 헥사고날 아키텍처 적용해서
// CQRS 패턴으로 이벤트 소싱하면서 사가 패턴으로
// 분산 트랜잭션 처리하면 되지 않나요?"
// 팀원: "...그러니까 구체적으로 어떻게 하자는 거예요?"
// "아 그건... 일단 도메인 레이어부터 나누고..."
// (실제로 DDD를 적용해본 적은 없음)
// (헥사고날 아키텍처는 유튜브 영상 하나 봤음)
// (CQRS는 블로그 글 읽었음)
// (이벤트 소싱은 단어만 알고 있음)
// 진짜 시니어의 반응:
// "그 패턴들 다 좋지만, 우리 프로젝트는 사용자 5천 명이고
// 개발자 3명이에요. 모놀리스로 충분합니다.
// 복잡한 아키텍처는 복잡한 문제가 생겼을 때 도입해도 늦지 않아요."
용어 폭격의 위험
기술 용어를 많이 아는 것과 실제로 적용할 수 있는 것은 다름.
용어를 많이 쓴다고 시니어가 되는 게 아님. 오히려 진짜 시니어일수록 쉬운 말로 설명할 수 있음.
"이건 헥사고날 아키텍처의 포트 앤 어댑터 패턴입니다" vs "외부 시스템이 바뀌어도 핵심 로직은 안 바뀌게 하려고 이렇게 나눈 거예요"
같은 내용인데 두 번째가 더 시니어임.
패턴 2: 실체 없는 아키텍처
// 시니어인 척 주니어가 설계한 아키텍처
// "마이크로서비스로 가야 합니다!"
// 현재 상황:
// - 사용자 수: 5,000명
// - 개발자 수: 3명
// - 서비스: 1개 (모놀리스)
// - 월 매출: 500만원
// 제안한 아키텍처:
// - API Gateway (Kong)
// - 서비스 디스커버리 (Consul)
// - 서비스 메시 (Istio)
// - 메시지 큐 (Kafka)
// - 분산 캐시 (Redis Cluster)
// - 컨테이너 오케스트레이션 (Kubernetes)
// - 모니터링 (Prometheus + Grafana + Jaeger)
// - 마이크로서비스: 12개
// 진짜 시니어: "이거 운영할 사람이 누구죠?"
// 시니어인 척 주니어: "DevOps팀이..."
// 진짜 시니어: "DevOps팀 없는데요. 우리가 3명인데요."
// 시니어인 척 주니어: "..."
// 결론: 아키텍처는 조직의 규모와 능력에 맞아야 함.
// 넷플릭스 아키텍처를 5명짜리 스타트업에 적용하면
// 그건 아키텍처가 아니라 자살 행위임.
패턴 3: 코드 리뷰 트집러
// 시니어인 척 주니어의 코드 리뷰 스타일
// PR 코드:
const isActive = user.status === 'active';
// 리뷰 코멘트:
// "이거 enum으로 안 뽑나요? 매직 스트링이잖아요.
// 그리고 변수명도 isUserActive가 더 명확한 것 같고요.
// 이왕이면 유틸 함수로 빼서 재사용할 수 있게 하면 좋겠습니다.
// 아, 그리고 타입 가드 패턴 적용하면 더 좋을 것 같아요."
// 실제로 중요한 리뷰 포인트:
// - 이 변경이 비즈니스 로직에 맞는지
// - 에러 케이스를 처리했는지
// - 성능에 영향이 있는지
// → 이런 건 안 봄
// 진짜 시니어의 코드 리뷰:
// "로직은 맞는데, 한 가지 질문:
// suspended 상태의 사용자는 어떻게 처리해야 하나요?
// 기획서에 없는 것 같은데 PM한테 확인해볼까요?"
// → 나무가 아니라 숲을 봄
패턴 4: 과도한 추상화
// 시니어인 척 주니어가 만든 "확장 가능한" 코드
// 요구사항: 사용자 이름을 가져오기
// 시니어인 척 주니어 버전:
interface IUserNameProvider {
getUserName(userId: string): Promise<string>;
}
interface IUserNameProviderFactory {
createProvider(type: string): IUserNameProvider;
}
class DatabaseUserNameProvider implements IUserNameProvider {
constructor(private repository: IUserRepository) {}
async getUserName(userId: string): Promise<string> {
const user = await this.repository.findById(userId);
return new UserNameFormatter(
new UserNameFormatterConfig({ locale: 'ko-KR' })
).format(user.name);
}
}
class UserNameProviderFactory implements IUserNameProviderFactory {
createProvider(type: string): IUserNameProvider {
switch (type) {
case 'database':
return new DatabaseUserNameProvider(
new UserRepository(new DatabaseConnection())
);
case 'cache':
return new CachedUserNameProvider(
new DatabaseUserNameProvider(
new UserRepository(new DatabaseConnection())
),
new CacheService()
);
default:
throw new Error(`Unknown provider type: ${type}`);
}
}
}
// 진짜 시니어 버전:
async function getUserName(userId: string): Promise<string> {
const user = await db.users.findById(userId);
return user.name;
}
// "나중에 확장할 수 있게" 라는 건 좋은 의도지만
// 지금 필요하지 않은 추상화는 복잡성만 늘림.
// YAGNI (You Ain't Gonna Need It) 원칙.
// 필요해지면 그때 추상화해도 됨.
과도한 엔지니어링의 비용
위 예시에서:
- 시니어인 척 주니어 버전: 파일 5개, 클래스 5개, 인터페이스 3개, 약 80줄
- 진짜 시니어 버전: 함수 1개, 3줄
둘 다 "사용자 이름 가져오기"를 수행함. 어떤 게 유지보수하기 쉬운지는 명확함.
"확장 가능한 코드"는 실제로 확장이 필요할 때 만드는 것. 미리 만들면 그건 확장이 아니라 부채임.
주니어인 척하는 시니어의 패턴
이건 반대 방향의 문제인데, 의외로 흔함. 경력이 많은데 의도적으로 주니어처럼 행동하는 시니어들.
패턴 1: 겸손을 가장한 회피
// 기술 결정 회의에서:
// PM: "이번 프로젝트 아키텍처를 어떻게 가져가면 좋을까요?"
// 주니어인 척 시니어:
// "음... 저도 잘 모르겠는데요. 다들 어떻게 생각하세요?"
// (실제로는 10년 경력에 비슷한 프로젝트 5번 해봤음)
// (답을 알지만 책임지기 싫어서 안 말함)
// (잘못된 결정의 책임을 지고 싶지 않음)
// 주니어들: (시니어가 모른다니까 우리가 결정해야 하나...)
// → 경험 없는 팀이 경험 없이 결정을 내림
// → 예측 가능한 실패
// 이게 "겸손"이 아닌 이유:
// 진짜 겸손 = "제 경험으로는 A 방식이 좋았는데,
// 다른 의견 있으면 듣고 싶어요"
// 가짜 겸손 = "모르겠어요~" (알면서 안 말함)
패턴 2: 의사결정 미루기
// 주니어인 척 시니어의 의사결정 패턴
interface DecisionLog {
date: string;
decision: string;
owner: string;
}
const 주니어인척시니어_의사결정: DecisionLog[] = [
{
date: "1주차",
decision: "DB 선택",
owner: "좀 더 조사해보고 결정하죠",
},
{
date: "3주차",
decision: "DB 선택",
owner: "벤치마크 결과 보고 결정하죠",
},
{
date: "5주차",
decision: "DB 선택",
owner: "팀원들 의견 좀 더 모아보죠",
},
{
date: "7주차",
decision: "DB 선택",
owner: "다음 주에 결정하죠",
},
{
date: "9주차",
decision: "DB 선택",
owner: "... 일단 MySQL 쓰죠 (시간 없으니까)",
},
];
// 9주 동안 결정을 미룬 결과:
// - 1주차에 결정했으면 됐을 걸 9주 동안 질질 끌음
// - 결국 가장 안전한(하지만 최적은 아닌) 선택으로 귀결
// - 팀원들은 방향 없이 9주 동안 임시 코드를 짬
// - 시니어의 존재 의미: ???
패턴 3: "내가 하면 빠른데" 증후군의 반대
// 주니어인 척 시니어의 위임 패턴
// 정상적인 위임:
// "이 기능은 A가 맡아주세요.
// 이런 부분은 주의가 필요하고,
// 막히면 저한테 물어보세요."
// 주니어인 척 시니어의 위임:
// "이 기능... 누가 맡을래요?"
// (방향을 안 잡아줌)
// (주의사항도 안 알려줌)
// (막혔을 때 물어보면 "음... 검색해보세요" 라고 함)
// 이건 위임이 아니라 방치임.
// 주니어가 삽질하는 시간 = 시니어가 가이드 안 해준 비용.
왜 시니어가 주니어인 척 할까?
1. 번아웃: 오랫동안 리드 역할을 하다 지쳐서 책임 회피 2. 정치적 계산: 잘못된 결정의 책임을 지고 싶지 않음 3. 조직 불신: "어차피 내 의견 반영 안 될 건데" 4. 이직 준비: 곧 나갈 건데 깊이 관여하고 싶지 않음 5. 피터의 법칙: 시니어 레벨까지 승진했지만 실제 능력은 미드레벨
어떤 이유든 결과는 같음: 팀에 시니어가 있는데 시니어 역할을 아무도 안 하는 공백 상태.
패턴 4: 기술 부채를 방관하는 시니어
// 주니어: "이 코드 좀 리팩토링해야 할 것 같은데요..."
// 주니어인 척 시니어: "음... 지금은 기능 개발이 우선이니까요"
// 주니어: "테스트 커버리지가 10%인데 올려야 하지 않나요?"
// 주니어인 척 시니어: "시간 나면 해요"
// 주니어: "이 라이브러리 2년 전 버전인데 업데이트해야..."
// 주니어인 척 시니어: "동작하는데 건드릴 필요 있나요?"
// 6개월 후:
// - 기술 부채 산더미
// - 새 기능 개발 속도 50% 하락
// - 장애 빈도 3배 증가
// - 주니어들: "시니어가 이러면 우리가 뭘 어쩌라고..."
// 진짜 시니어라면:
// "이번 스프린트에서 기술 부채 해소에 20% 할애합시다.
// 이 3개는 우선순위가 높으니 이번에 처리하고,
// 나머지는 다음 스프린트에서 하죠."
// → 방향을 잡아주는 게 시니어의 역할
진짜 성장의 지표
그러면 진짜 시니어가 되려면 어떻게 해야 하는가. 연차를 채우는 게 아니라, 무엇이 달라져야 하는가.
기술적 성장 지표
// 주니어의 기술적 특성
const junior = {
problemSolving: "주어진 문제를 해결함",
codeQuality: "동작하는 코드를 작성함",
debugging: "console.log로 디버깅함",
testing: "수동 테스트",
architecture: "프레임워크가 제공하는 구조를 따름",
communication: "기술적 질문을 함",
};
// 시니어의 기술적 특성
const senior = {
problemSolving: "올바른 문제를 정의하고, 풀지 않아도 될 문제를 식별함",
codeQuality: "팀 전체의 코드 품질 기준을 설정하고 유지함",
debugging: "시스템적으로 원인을 추적하고, 재발 방지책을 세움",
testing: "테스트 전략을 설계하고, 무엇을 테스트할지 판단함",
architecture: "비즈니스 요구사항에 맞는 아키텍처를 설계하고 진화시킴",
communication: "기술적 결정을 비개발자에게 설명할 수 있음",
};
// 차이는 "더 잘한다"가 아니라 "다른 차원의 일을 한다"
비기술적 성장 지표
// 사실 시니어와 주니어의 가장 큰 차이는 기술이 아님
interface SeniorSoftSkills {
// 1. 불확실성 내성
// 주니어: "정답이 뭐예요?"
// 시니어: "정답은 없고, 현재 상황에서 가장 나은 선택은 이거예요.
// 근거는 A, B, C이고, 리스크는 X, Y예요."
ambiguityTolerance: 'high';
// 2. 트레이드오프 사고
// 주니어: "A가 B보다 좋다"
// 시니어: "A는 이런 상황에서 좋고, B는 저런 상황에서 좋다.
// 우리 상황은 이러니까 A가 나을 것 같다."
tradeoffThinking: 'mature';
// 3. 갈등 해결
// 주니어: "내가 맞아!" vs "내가 맞아!"
// 시니어: "둘 다 일리가 있는데, 우리의 제약 조건을 고려하면
// 이렇게 하는 게 현실적이에요."
conflictResolution: 'constructive';
// 4. 멘토링
// 주니어: (혼자 성장하기 바쁨)
// 시니어: 다른 사람의 성장을 도움으로써 팀 전체의 역량을 올림
mentoring: 'active';
// 5. 비즈니스 이해
// 주니어: "기획서대로 만들었습니다"
// 시니어: "기획서에 이런 부분이 빠져 있는데,
// 사용자 입장에서 이건 필요할 것 같아요."
businessAwareness: 'proactive';
}
시니어가 되는 현실적인 경로
1단계 (1-2년차): 자기 태스크 완수
- 주어진 업무를 독립적으로 수행
- 기술의 기본기를 탄탄히
2단계 (2-4년차): 팀 기여
- 코드 리뷰에서 의미 있는 피드백 제공
- 기술 부채를 인식하고 해소 제안
- 주니어의 질문에 답변할 수 있음
3단계 (4-7년차): 팀 영향력
- 기술적 방향을 제시하고 실행
- 팀의 프로세스/문화에 기여
- 다른 팀과의 기술적 소통
4단계 (7년차+): 조직 영향력
- 여러 팀에 걸친 기술적 전략
- 시니어/리드를 멘토링
- 기술 조직의 방향 설정
주의: 연차는 참고일 뿐. 2단계에서 10년 있는 사람도 있고, 3년 만에 3단계에 도달하는 사람도 있음.
자가 진단 & 성장 가이드
시니어인 척 주니어 탈출법
// 용어 폭격기에서 탈출하려면:
// Before: "헥사고날 아키텍처로 DDD 적용해서..."
// After: (실제로 해보기)
// Step 1: 사이드 프로젝트에서 직접 적용해보기
// → 이론과 실전의 갭을 체감
// Step 2: 실패 경험 쌓기
// → "마이크로서비스 적용했다가 운영 못 해서 모놀리스로 돌아간 경험"
// → 이런 실패 경험이 진짜 시니어를 만듦
// Step 3: "왜?"를 5번 물어보기
// → "마이크로서비스가 좋다"
// → "왜?" "확장이 쉬우니까"
// → "왜 확장이 필요한데?" "사용자가 늘 수 있으니까"
// → "지금 사용자가 몇 명인데?" "5천 명..."
// → "5천 명에 마이크로서비스가 필요한가?" "... 아닌 것 같기도"
// → 5번째 "왜?"에서 진실이 나옴
주니어인 척 시니어 탈출법
// 의사결정 회피에서 탈출하려면:
// Before: "음... 잘 모르겠는데요"
// After: "제 경험으로는 A를 추천합니다. 이유는..."
// Step 1: 의견을 먼저 내기
// → 틀려도 됨. 시니어가 틀리는 건 자연스러운 거임.
// → 의견이 없는 시니어가 더 문제임.
// Step 2: 결정에 대한 책임 받아들이기
// → "내가 추천한 거니까 문제 생기면 내가 해결하겠다"
// → 이 한마디가 팀에 주는 안도감이 엄청남
// Step 3: 메타 커뮤니케이션
// → 번아웃이면 솔직히 말하기
// → "요즘 좀 지쳐서 리드 역할을 잠시 내려놓고 싶다"
// → 숨기면 팀 전체가 피해봄
마무리: 레벨은 이름표가 아니라 행동이다
시니어인 척 주니어:
- 용어는 많이 알지만 실행 경험이 부족
- 과도한 아키텍처를 제안하지만 운영 경험이 없음
- 코드 리뷰에서 나무만 보고 숲을 못 봄
→ 처방: 실행하기. 실패하기. 배우기. 겸손해지기.
주니어인 척 시니어:
- 경험은 있지만 책임을 회피함
- 의견을 숨기고 결정을 미룸
- 팀에 방향을 제시하지 않음
→ 처방: 목소리 내기. 책임지기. 팀을 이끌기.
진짜 시니어:
- 상황에 맞는 기술을 선택함 (화려한 게 아니라 적절한 것)
- 팀의 방향을 설정하고 책임짐
- 다른 사람을 성장시킴
- 모르는 건 모른다고 말하고, 아는 건 공유함
- 실패를 두려워하지 않되, 실패에서 배움
가장 중요한 것
시니어인 척하지 말고, 주니어인 척하지도 말고, 지금 자기가 있는 레벨에서 최선을 다하면 됨.
성장은 직선이 아니라 계단형임. 한동안 정체된 것 같다가도 갑자기 한 단계 올라가는 순간이 옴.
그 순간은 "남들이 시니어라고 불러줄 때"가 아니라 "내가 팀에 진짜 도움이 되고 있다고 느낄 때" 옴.
타이틀은 협상의 결과이고, 실력은 행동의 결과임.
둘 다 중요하지만, 순서가 있음. 실력 먼저, 타이틀은 따라옴.
"시니어 개발자는 만들어지는 게 아니라, 증명되는 것이다. 이력서가 아니라 행동으로."