📝 개인 공부 및 자료 정리 목적의 포스팅입니다.
내용은 지속적으로 추가될 예정이고, 보다 자세하게 정리할 필요성을 느끼게 될 시엔 별도의 포스팅을 작성 후 링크를 첨부할 계획입니다.
(글이 길어지는 관계로 아직 작성되지 않은 항목이 있을 수 있습니다.)
모든 질문은 여러 블로그를 참고하고, 실제로 겪은 질문을 기반으로 작성합니다.
답변 또한 스스로 생각하거나, 실제 답변한 내용을 기반으로 작성하다보니 틀린 내용이 있을 수 있습니다. (찾아서 지적해주시면 정말 감사드리겠습니다. 🥲)
참고한 모든 포스팅은 제일 하단에 정리해두었습니다.
1. 신입 개발자 기술 면접 정리 : 공통 항목 (1/12)
2. 신입 개발자 기술 면접 정리 : 운영체제 (2/12)
3. 신입 개발자 기술 면접 정리 : 알고리즘 (3/12)
4. 신입 개발자 기술 면접 정리 : 자료구조 (4/12)
5. 신입 개발자 기술 면접 정리 : 컴퓨터 구조 (5/12)
6. 신입 개발자 기술 면접 정리 : 데이터베이스 (6/12)
7. 신입 개발자 기술 면접 정리 : 네트워크 (7/12)
8. 신입 개발자 기술 면접 정리 : Java (8/12)
9. 신입 개발자 기술 면접 정리 : Spring Framework (9/12)
10. 신입 개발자 기술 면접 정리 : 백엔드 (10/12)
11. 신입 개발자 기술 면접 정리 : 시스템 설계 (11/12)
12. 신입 개발자 기술 면접 정리 : 개별 맞춤형 & 인성 (12/12)
📌 클래스란 무엇이고, 인터페이스와는 어떤 점이 다른가요?
클래스는 객체 지향에서 가장 작은 단위, 즉 가장 구체화된 객체 분류라고 할 수 있습니다.
클래스보다 더 자르면 속성(데이터)과 행동(메서드)으로 나뉘기 때문에 타입으로 볼 수 없기 때문입니다.
이러한 다양한 클래스들을 개발자가 하나의 공통 그룹으로 묶고, 그룹 내 클래스들에게 어떤 행동을 반드시 구현하도록 만들고 싶을 때 사용하는 것이 인터페이스입니다.
예를 들어, 강아지, 개구리, 사람이라는 클래스가 존재한다고 가정해보겠습니다.
개발자가 "이런 행동을 하는 것들을 포유류라고 하자"고 정의하고, 강아지와 사람이라는 클래스를 묶을 수 있습니다.
반면, 개발자가 "이런 행동을 하는 것들을 동물이라고 하자"고 정의하고, 모든 클래스를 하나로 묶을 수도 있습니다.
중요한 것은 클래스는 실제 구현체를 가지며 인스턴스화 될 수 있지만, 인터페이스는 "어떤 기능을 반드시 구현해야 한다"는 명세를 정의한 추상적인 타입입니다.
인터페이스는 메서드의 시그니처만 정의하고, 실제 구현을 이를 구현하는 클래스에 위임합니다.
(여기서 꼬리 질문 들어오면, 항상 SOLID 원칙 파트로 넘어감. 해당 내용으로 받아칠 수 있음.)
📌 git이 무엇인가요?
git은 분산 버전관리 시스템(DVCS; Distributed Version Control System)입니다.
쉽게 설명하면 형상 기억을 위한 소프트웨어라고 할 수 있습니다.
이는 각 개발자가 전체 저장소의 복사본을 로컬에 가지고 있으면서, 코드의 변경 사항을 효율적으로 추적하고 관리할 수 있게 해줍니다.
git이 없다면 개발자는 소스 코드의 버전을 구분하기 위해 매번 전체 코드를 압축하고, 압축 파일의 이름을 날짜 별로 정리해야 하는데, 이는 상당히 번거롭고 많은 디스크 공간을 할애해야 합니다.
하지만 git을 사용하여 버전을 관리하면 개발자는 git 명령어를 통해 쉽게 코드의 버전을 관리할 수 있고,
각 버전의 스냅샷(snapshot)을 찍어 작은 청크 파일로 압축하여 저장하기 때문에
코드 유지 보수성을 향상시키고, 디스크 공간을 크게 절약할 수 있습니다.
🟡 Git의 3가지 영역에 대해 설명해주세요.
git은 Working Directory, Staging Area, Repository 3개의 영역을 기준으로 소스 코드를 관리합니다.
- Working Directory: 작업 중인 디렉토리
- Staging Area: git add 명령어로 변경 사항을 추적 중인 파일이 모여있는 공간
- Repository: git commit 명령어로 변경 작업들을 영구 저장한 장소
Working Directory에서 코드를 작성하고 git add를 하면, 변경된 작업 사항들이 Staging Area에 등록됩니다.
그리고 git commit을 하면 Staging Area에 등록된 변경 사항들이 Repository에 저장됩니다.
🟡 Staging Area의 필요성은 무엇인가요?
Staging Area가 없으면, "커밋될 예정인 변경 사항"들을 관리하기가 어려워지기 때문입니다.
이러한 변경 사항들은 commit이 되기 전까지 디스크에 저장될 수 없으므로, 모두 메모리에 적재된 상태여야 합니다.
그런데 만약 commit 이전에 해당 프로세스가 죽거나, 실수로 죽인 경우 모든 변경 사항이 유실될 수 있습니다.
만약 큰 규모의 작업 혹은, 병합 충돌 시 모든 변경 파일을 상당 시간 메모리에만 올려두고 작업해야 하는데, 이는 위험성이 높기 때문에 차라리 Staging Area를 만들어 디스크의 어떤 공간에 저장해두는 것입니다.
또한 staging area가 존재할 때는 "git diff -staged"와 같은 명령어로 쉽게 변경 사항을 확인할 수 있습니다.
하지만 이것이 메모리에 올라와있는 상태라면, 해당 메모리 영역을 사용하고 있는 프로세스를 찾아서 물어봐야지만 알 수 있기 때문에, git의 구현 난이도가 매우 어려워질 수밖에 없습니다.
🟡 Github은 무엇인가요?
프로그래머가 작업한 git 파일들을 다른 개발자들과 공유하기 위해 사용하는 원격 저장소를 제공해주는 웹 호스팅 서비스입니다.
git 저장소를 특정 github 저장소와 연결한 후, git push를 하면 Repository 영역에 저장된 소스 코드들을 원격 브랜치로 저장합니다.
Github은 원격 저장소 이외에도 많은 서비스를 제공하는데, CI/CD 파이프라인을 구축하기 위한 github actions, git branch 규칙 설정, 팀원의 권한 설정들을 수행할 수 있습니다.
Github 외에도 Gitlens와 같은 다른 원격 저장소를 사용할 수도 있습니다.
🟡 Git branch란 무엇인가요?
git은 commit을 하면 내부적으로 이전 커밋과 연결을 수행하는데, 이렇게 여러 commit 내역들이 마치 하나의 선으로 표현됩니다.
이렇게 처음 형성되는 포인터 흐름을 master 브랜치, 최근에는 main 브랜치라고 부릅니다.
그러나 협업 환경에서 하나의 main 브랜치에 작업을 수행하면 수많은 충돌이 발생하게 될 것이고, 이로 인해 개발 생산성이 저하될 우려가 있습니다.
따라서 main 브랜치에서 브랜치를 분기하여 독립적인 영역에서 개발을 한 후, 마지막에 한 번에 main 브랜치에 병합합니다.
이렇게 작업을 이어나가면, 마치 가지가 여러 갈래로 뻗어나가는 형상을 보이기 때문에 git tree라고 부릅니다.
그리고 main 브랜치를 포함하여, 여러 곳에서 뻗어나가는 commit 포인터 줄기들을 브랜치라고 칭합니다.
🟡 Git 브랜치 전략이 무엇인가요? 어떤 프로젝트에, 어떤 전략을 선택하는 것이 좋은가요?
가장 대표적인 전략은 github-flow branch 전략과 git-flow branch 전략이 있습니다.
- git-flow branch 전략
- 일반적으로 가장 많이 선택하는 전략
- 통상적인 브랜치 규칙은 다음과 같다.
- main: 실제 런칭되는 제품 코드
- develop: 다음 버전에 배포할 제품 코드를 개발하는 브랜치
- feature: 추가 기능을 개발하는 브랜치. develop 브랜치에서 분기한다. (fix 브랜치를 구분하기도 함)
- release: 다음 버전 출시를 준비하는 브랜치. develop 브랜치를 release 브랜치를 옮기고, QA 및 테스트를 진행하고 main 브랜치로 병합한다.
- hotfix: main 브랜치에서 발생한 버그를 빠르게 수정하는 브랜치.
- 브랜치 규칙들을 상세하게 설정하고, 목적 별로 구분하기 쉽다.
- 너무 많은 분기점이 존재해서 git tree가 복잡해진다.
- github-flow branch 전략
- main 브랜치를 언제나 최신 상태로 유지하는 전략
- git-flow 전략에 비해 단순한 구조를 갖는다. (하나의 base 브랜치 (main) + main에 기능을 추가하기 위한 브랜치)
- 엄격한 규칙을 갖는 main 브랜치를 제외하고, 나머지 브랜치에 대한 명확한 규약이 없다.
- 단순하고 지속적인 배포를 강조하며, 브랜치명과 커밋 메시지의 명확성, 주기적인 커밋 내역 공유, Pull Request 리뷰 등을 중요시 한다.
🟡 Rebase에 대해 설명해주세요.
두 개의 공통 베이스를 가진 브랜치에서, 한 브랜치의 베이스를 다른 브랜치의 최신 커밋으로 옮기는 작업입니다.
여기서 "옮긴다"는 것은 비유적인 표현이고, 실제로는 커밋들이 재생성됩니다.
보다 쉽게 설명하자면, git rebase는 한 브랜치의 변경사항을 다른 브랜치에 순차적으로 적용하는 과정입니다.
merge와 달리, rebase는 커밋 히스토리를 선형적으로 만들어주는 특징이 있습니다.
# rebase 전
main: A -> B -> C
feature: B -> D -> E
# rebase 후
main: A -> B -> C -> D' -> E'
예를 들어, feature 브랜치에서 작업하는 동안 main 브랜치에 새로운 변경사항이 생겼다고 가정해보겠습니다.
이때 `git rebase main`을 실행하면, feature 브랜치의 변경사항들이 임시로 저장되었다가 main 브랜치의 최신 커밋 위에 하나씩 재적용됩니다.
rebase의 장점은 커밋 히스토리가 깔끔하게 정리된다는 것이지만,
이미 원격 저장소에 push된 브랜치를 rebase를 하는 경우엔 주의가 필요합니다.
이 경우 이미 동기화된 브랜치와 rebase로 생성된 브랜치의 충돌로 인해 머지 커밋을 해소하기 위해 강제 푸시(`git push -f`)를 수행해야 하는데, 개인이 개발하기 위해 만든 브랜치가 아닌 main 혹은 develop 브랜치라면 강제 푸시로 인한 결과가 어떻게 될 지 고려해봐야 합니다.
🟡 Squash Merge에 대해 설명해주세요.
여러 개의 커밋을 하나의 커밋으로 압축하여 병합하는 방법입니다.
예를 들어, feature 브랜치에서 10개의 커밋을 만들었다고 해도, main 브랜치에는 하나의 커밋으로 압축한 하나의 커밋만을 main 브랜치에 반영할 수 있습니다.
이 방식의 가장 큰 장점은 main 브랜치의 commit history를 깔끔하게 유지할 수 있다는 것입니다.
작은 수정사항이나 중간 과정 커밋들을 통합하여, 최종 변경사항이라는 의미있는 하나의 커밋으로 남길 수 있습니다.
단, 개별 커밋의 자세한 히스토리가 사라진다는 단점이 있습니다.
때에 따라 일반 merge 혹은 rebase를 사용하는 것이 더 적절할 수 있으며, 특정 시점의 변경 사항을 자세히 추적해야 하는 경우에는 squash merge를 지양하는 것이 좋습니다.
기능 개발이 완료되어 main 브랜치에 병합만을 필요로 할 때는 squash merge가 유용하고,
개발 브랜치 간의 동기화에는 일반 merge나 rebase가 더 적절할 수 있습니다.
📌 애자일 방법론에 대해 설명하세요.
변화에 유연하게 대응하면서 소프트웨어를 개발하는 개발 문화 내지 개발 방법론입니다.
전체 개발 과정을 작은 단위의 스프린트로 나누어 진행하며, 각 스프린트마다 계획, 개발, 테스트, 배포의 전체 사이클을 수행합니다.
마치 폭포수 모델을 마이크로 단위의 작업으로 반복 수행하는 것과 비슷합니다.
애자일 방법의 장점은 프로젝트 관계자들이 작은 목표에 집중할 수 있도록 돕고,
수요 주도 이론에 따른 클라이언트의 요구 사항을 빠르게 반영할 수 있다는 것에 있습니다.
하지만 애자일을 단순히 방법론으로 이해한다면, 애자일에서 사용하는 도구들을 도입하는 것으로 그칠 수 있습니다.
진정으로 애자일하게 일을 하려면, 문화를 개선하기 위해 많은 노력이 필요합니다.
🟡 폭포수 모델과의 차이를 설명해주세요.
폭포수 모델은 과거 기술 주도 이론 방법에 기반한 제품 개발 방법론입니다.
요구사항 분석부터 설계, 구현, 테스트, 배포까지 각 단계가 명확히 구분되어 있고, 전 단계가 모두 끝나면 다음 단계로 진행합니다.
- 요구사항 변경에 대한 접근
- 폭포수: 초기에 모든 요구 사항을 정확히 정의하고, 이후 변경을 최소화하는 방향으로 진행
- 애자일: 개발 중에도 요구사항이 변경될 수 있음을 전제로 하고, 빠르게 변화에 따라갈 수 있는 방향으로 진행
- 클라이언트와의 상호작용
- 폭포수: 프로젝트 초기와 말기에 소통
- 애자일: 매 스프린트마다 클라이언트의 피드백을 받고, 즉시 반영
- 위험 관리
- 폭포수: 초기 단계에서 대부분의 위험을 식별하고 대비
- 애자일: 각 스프린트마다 위험을 지속적으로 평가하고 대응
- 산출물 배포
- 폭포수: 모든 개발이 완료된 후 한 번에 배포
- 애자일: 작은 기능 단위로 자주 배포하여 빠른 피드백 수용
이론적으로 폭포수가 애자일보다 언제나 좋지 않다고 생각할 수 있지만, 프로젝트에 따라 폭포수가 더 적절할 수 있습니다.
예를들어, 요구사항이 명확하고 변경 가능성이 낮은 프로젝트, 높은 수준의 안정성이 요구되는 프로젝트, 데드라인이 매우 짧은 프로젝트 등에서는 폭포수 모델이 더 적절할 수도 있습니다.
🟡 (개별 경험) 애자일 방법론을 적용하여 프로젝트를 진행해본 경험이 있나요? 어떻게 진행하고, 어떤 깨달음을 얻었나요?
넹. 재밌었어용. 아뇨, 사실 힘들었어요..
📌 Override와 Overload를 설명하세요.
오버라이드는 재정의, 오버로드는 중복 정의 입니다.
보다 자세히 설명하자면, 오버라이딩은 상속 관계에서 부모 클래스의 메서드를 자식 클래스에서 재정의하는 것을 의미합니다.
부모 클래스에서는 A라는 방식으로 동작하는 기능이, 자식 클래스에서는 B라는 방식으로 동작하도록 만들 수 있습니다.
이는 객체 지향에서 다형성을 구현하는 핵심 방법 중 하나입니다.
오버로드는 같은 이름의 메서드를 매개변수의 타입이나 개수를 다르게 하여 여러 개 정의하는 것입니다.
예를 들어, 계산기의 더하기 기능을 구현한다고 가정해보겠습니다.
더하기라는 함수를 구현할 때, 두 파라미터의 타입이 int 일 수도, long일 수도, 혹은 다른 타입을 가질 수 있습니다.
이런 경우 add라는 이름의 함수를 여러 개 정의하여, 타입별로 동일한 이름을 사용하는 함수들을 오버로드할 수 있습니다.
🟡 Overload의 주의점에 대해 설명해주세요.
- 반환 타입만 다른 경우는 오버로딩이 성립하지 않습니다. 컴파일러가 어떤 메서드를 호출할지 구분할 수 없기 때문입니다.
- 매개변수 이름만 다른 것은 오버로딩이 아닙니다.
- 접근 제어자가 다른 것만으로는 오버로딩이 성립하지 않습니다.
- 제네링 타입이 다른 경우도 타입 소거로 인해 오버로딩이 되지 않습니다.
📌 컴파일 언어와 인터프리터 언어의 차이에 대해 설명하세요.
컴파일 언어는 컴파일러를 통해 컴파일 타임에 전체 소스 코드를 한 번에 기계어로 변환 후 실행파일을 만듧니다.
컴파일러 언어는 컴파일 단계와 실행 단계가 분리되어 있기 때문에, 컴파일은 단 한 번만 수행합니다.
실행 단계에선 컴파일의 결과로 생성된 실행 파일을 실행만 하면 되므로 코드 실행 속도가 빠르지만,
규모가 큰 프로젝트의 경우 컴파일 타임이 오래 걸릴 수 있다는 단점이 존재합니다.
또한 크로스 플랫폼 실행을 위해서는 각 운영체제별로 다시 컴파일해야 한다는 제약이 있습니다.
C, C++이 대표적인 컴파일 언어입니다.
반면, 인터프리터 언어는 컴파일하지 않고 소스 코드를 한 줄씩 읽어들여 실행합니다.
컴파일로 인한 시간 소요는 없기 때문에 빠른 개발과 즉각적인 테스트가 가능하며, 플랫폼 독립적으로 실행할 수 있다는 장점이 있습니다.
하지만 실행 파일을 별도로 생성하지 않기 때문에, 실행할 때마다 코드를 해석해야 하므로 실행 속도가 상대적으로 느리다는 단점이 있습니다.
Python, Javascript, Ruby가 대표적인 인터프리터 언어에 속합니다.
🟡 Java도 컴파일 언어인가요?
Java는 하이브리드 접근 방식을 취합니다.
소스 코드는 먼저 바이트 코드(.class 파일)로 컴파일 되고, 이 바이트 코드는 JVM에서 인터프리터 방식으로 실행됩니다.
더불어 JIT(Just-In-Time) 컴파일러를 통해 자주 실행되는 코드를 기계어로 컴파일하여 성능을 최적화 합니다.
이러한 특징으로 인해, 자바는 컴파일 단계가 존재하지만 플랫폼 독립성("Write Once, Run Anywhere")을 보장합니다.
(컴파일에 대해 더 자세히 물어보면 컴퓨터 구조 CS 파트로 넘어감. Java 개발자의 경우 JVM으로 넘어가기도 함.)
📌 어노테이션이란 무엇인가요?
(자바가 주언어라 자바 중심으로 작성. 다른 언어에서도 각기 다른 어노테이션 사용 전략이 존재하므로 찾아보면 좋을 듯 합니다.)
직역을 하면 주석이지만, 일반 주석보다 개발에 유용한 정보를 제공합니다.
어노테이션은 코드에 메타데이터를 추가하는 방법으로, 프로그램의 동작에 직접적인 영향을 주지 않으면서도 코드의 의도나 동작 방식을 지정할 수 있게 해줍니다.
자바에서는 "@" 기호를 사용하여 표시하며, 리플렉션을 통해 다음과 같은 동작을 구현할 수도 있습니다.
- 컴파일러가 문법 에러를 체크 하도록(혹은 하지 않도록) 정보를 제공한다.
- 프로그램을 빌드할 때 코드를 자동으로 생성할 수 있는 정보를 제공한다.
- 런타임에 특정 기능을 실행하도록 정보를 제공한다.
예를 들어, 자바의 @Override나 @Deprecated와 같이 컴파일러에게 특정 지시사항을 전달하거나,
Lombok 라이브러리의 @Getter, @Setter처럼 실제 코드를 자동으로 생성하게 합니다.
Spring의 @Autowired나 JPA의 @Entity처럼 프레임워크가 런타임에 특별한 처리를 하도록 지시할 수도 있습니다.
JUnit에서는 명명 패턴을 사용하지 않고, @Test 어노테이션을 선언함으로써 테스트 타겟 메서드를 구분할 수 있습니다.
위에서 언급했듯, 자바에서는 리플렉션을 통해 어노테이션의 강점을 극대화할 수 있습니다.
(리플렉션 꼬리 물기 들어오면, 자바 언어 파트로 넘어감.)
(혹은 Spring이나 Lombok 주요 어노테이션으로 들어가는 경우도 많음.)
📌 브라우저란 무엇인가요?
브라우저는 웹 서버에서 제공하는 리소스들을 우리가 보기 쉽게 화면으로 보여주는 프로그램입니다.
사용자가 URL을 입력하여 목적지를 정하면, 브라우저는 해당 주소에서 웹 서버에서 필요한 파일들을 가져옵니다.
그리고 파일들을 사람이 이해하기 쉬운 형태로 변환합니다.
HTML은 웹 페이지 구조를, CSS는 디자인을 JavaScript는 동적인 기능을 나타내는데, 브라우저는 이것들을 해석하고 조합하여 우리가 익숙한 웹 페이지 형태로 보여줍니다.
대표적인 웹 브라우저로는 Chrome, Safari, Edge 등이 있으며, 각각의 브라우저는 같은 웹 표준을 따르지만 약간씩 다른 방식으로 구현되어 있어, 때로는 같은 웹 페이지도 브라우저마다 다르게 보일 수 있습니다.
🟡 브라우저의 작동 방식에 대해 설명하세요.
브라우저는 사용자가 URL을 입력하면 크게 두 가지 주요 과정을 거칩니다.
첫 번째는 서버로부터 필요한 리소스를 받아오고, 두 번째는 받아온 리소스를 화면에 그리는 렌더링 과정입니다.
먼저 리소스를 받아오는 과정을 보면, 브라우저는 입력된 URL의 도메인을 IP 주소로 변환하기 위해 DNS 조회를 수행합니다.
IP 주소를 얻으면 해당 서버와 TCP, UDP와 같은 프로토콜로 리소스를 받아옵니다.
만약, TCP 통신을 수행한다면 Three-way handshake를 수행하고, HTTPS의 경우 TLS handshake도 진행합니다.
다음으로 렌더링 과정에서는, 받아온 HTML 문서를 파싱으로 DOM 트리를 만들고, CSS를 파싱하여 CSSOM을 생성합니다.
이 둘을 결합하여 렌더 트리를 만든 후, 각 요소의 크기와 위치를 계산하는 레이아웃 과정을 거쳐, 최종적으로 화면에 그리는 페인팅 작업이 이루어집니다.
JavaScript의 경우, 파싱 과정 도중 만나면 파싱을 중단하고 JavaScript를 먼저 실행합니다.
이는 JS가 DOM을 조작할 수 있기 때문입니다.
따라서 성능 최적화를 위해, JS를 body 끝에 배치하거나 async, defer 속성을 사용하는 것이 좋습니다.
📌 SSR(Server-Side Rendering)과 CSR(Client-Side Rendering)의 차이에 대해 설명하세요.
서버 사이드 렌더링은 서버에서 완성된 HTML을 만들어서 클라이언트로 보내는 방식입니다.
클라이언트가 웹 사이트를 요청하면, 서버는 필요한 데이터를 통해 HTML을 완성한 후 브라우저로 보냅니다.
브라우저는 이미 완성된 HTML을 받아서 렌더링 동작을 수행하기만 하면 됩니다.
하지만 서버 사이드 렌더링은 매 페이지 요청마다 서버에서 새로운 HTML을 만들어야 하므로, 서버에 부하가 높아집니다.
또한 스마트폰의 도입 이후부터는 다양한 플랫폼마다 다른 UI를 제공해주어야만 했는데, 이는 서버가 클라이언트에 종속되는 문제를 발생시킵니다.
반면 클라이언트 사이드 렌더링은 서버는 데이터를 제공하는 역할만 하고, 완성된 UI를 그리는 역할은 클라이언트 플랫폼에서 담당하도록 만든 방법입니다.
웹브라우저의 경우 미리 받은 JS 파일을 통해 페이지를 완성하고, 모바일 앱은 각 플랫폼 언어를 통해 완성된 UI를 만듧니다.
이를 통해 사용자 상호작용이 풍부한 동적 웹 애플리케이션 구현에 용이하다는 장점이 있습니다.
🟡 그렇다면 CSR이 언제나 SSR보다 좋은가요?
클라이언트 사이드 렌더링이 비록 매끄러운 화면 전환과 서버 부하를 낮추는 이점이 존재하지만, 언제나 더 좋은 것은 아닙니다.
특히, 웹 브라우저에서 CSR의 가장 큰 문제점은 검색 엔진 최적화(SEO)에 용이하지 않다는 점입니다.
이는 JS가 마치 페이지를 이동하는 것처럼 보이도록 만들었을 뿐, 실제로는 하나의 페이지밖에 존재하지 않기 때문입니다.
또한 처음에 모든 JS 파일을 받아와야 하는 CSR에 비해, SSR은 초기 페이지 로딩이 빠릅니다.
이러한 이유로 최근에는 두 방식의 장점을 모두 활용하는 하이브리드 방식이 많이 사용된다고 알고 있습니다.
예를 들어, 초기 로딩 시 SEO와 성능이 중요한 페이지는 SSR로 처리하고, 사용자 인터렉션이 많은 대시보드나 관리자 페이지 같은 부분은 CSR로 처리하는 식으로 각 렌더링 방식의 장점을 상황에 맞게 활용할 수 있습니다.
Next.js와 같은 프레임워크를 사용하면 첫 페이지는 SSR로 빠르게 보여주고, 이후 페이지 전환은 CSR로 부드럽게 처리하는 방식을 제공하는 것으로 알고 있습니다.
📌 Singleton 패턴에 대해 설명하세요.
싱글톤 패턴은 애플리케이션 내에서 객체가 단 한 번만 생성된다는 유일성을 보장하기 위한 디자인 패턴입니다.
자바의 경우, 생성자의 접근 제한자를 private로 제한하고, 클래스 내부에 static 변수로 자기 자신을 인스턴스로 갖는 변수를 추가하여 구현할 수 있습니다.
만약 외부에서 싱글톤 클래스의 인스턴스를 필요로 할 때는 미리 만들어둔 인스턴스를 반환함으로써, 객체가 추가로 생성될 가능성을 원천 차단할 수 있습니다.
싱글톤 패턴은 주로 객체 생성 비용 비싼 클래스를 사용할 때 적용하는 패턴입니다.
대표적인 예로 데이터베이스 연결 모듈, 디스크 연결, 네트워크 통신, 스레드 풀, 로그 기록 객체 등에 이용합니다.
이러한 객체들은 새로 만들어서 사용할 필요가 없고, 그렇게 하더라도 불필요하게 메모리를 낭비할 뿐입니다.
따라서 애플리케이션에서 유일해야 하며, 유일한 것이 좋은 객체들을 싱글톤 객체로 만듧니다.
🟡 Signleton 패턴의 단점은 무엇인가요?
첫 번째로 싱글톤 패턴은 객체 지향 원칙을 반하는 디자인 패턴입니다.
외부에서 생성자를 호출할 수 없도록 생성자의 접근 제한자를 private로 고정하거나, final 클래스로 만들기 때문에 상속이 불가능합니다.
이로 인해, 다형성 원칙을 준수할 수 없으며, 클라이언트가 구체 클래스에 의존할 수밖에 없습니다.
이는 결국 SOLID 원칙 중 DIP와 OCP를 위반하는 결과를 낳습니다.
두 번째는 테스트가 어려워집니다.
싱글톤 인스턴스는 static 타입으로 애플리케이션 전역에서 상태를 공유하고 있기 때문에, 격리된 환경을 보장하기 위해서는 매번 인스턴스의 상태를 초기화해주어야 합니다.
그렇지 않으면, 싱글톤 객체가 다른 테스트에 영향을 받아 테스트가 실패할 수도 있습니다.
세 번째는 만약, 지연(Lazy) 생성 전략을 사용하려는 경우 동시성 문제를 고려해야 한다는 점입니다.
일반적으로 싱글톤 객체들은 생성 비용이 비싼 경우에 사용하기 때문에, 객체 생성을 보류하다가 실제 사용되는 시점에 초기화하는 전략을 택하기도 합니다.
하지만 인스턴스 초기화 여부를 검증하는 단계와 생성 단계의 원자적 연산이 보장되지 않기 때문에, 멀티 스레드 환경에선 인스턴스가 여러개 생성될 수도 있습니다.
락을 사용하거나, 이중검사 관용구 등의 기법을 사용하여 동시성 문제를 제어할 수는 있지만, 그만큼 싱글톤 클래스의 코드 복잡도는 증가하게 됩니다.
📌 MVC, MVP, MVVM 패턴에 대해 설명하세요.
MVC 패턴은 Model, View, Controller 계층으로 구성됩니다.
- Model: 데이터와 비즈니스 로직 담당
- View: 사용자에게 보여지는 화면 로직 담당
- Controller: Model과 View 사이의 상호 작용을 관리
가장 기본적이고 널리 사용되지만, View와 Controller의 강한 결합으로 인해 언제나 일대일로 대응하는 클래스를 생성해야 해서 Controller가 비대해지기 쉽고, 재사용성이 떨어집니다.
MVP 패턴은 Controller를 Presenter로 대체한 패턴입니다.
MVC와의 주요 차이점은 View와 Model이 직접 소통하지 않고, 반드시 Presenter를 통해서만 상호작용한다는 점입니다.
이는 View와 Model 사이의 결합도를 낮춤으로써, 테스트와 유지보수성을 높일 수 있는 장점을 제공합니다.
하지만 그만큼 Presenter의 책임이 많아져서 코드가 비대해질 우려가 있고, 모든 UI 이벤트를 Presenter로 전달해야 하므로 인터페이스 코드 또한 많이 필요합니다.
MVVM 패턴은 Model, View, ViewModel로 구성되며, 주로 프론트엔드 프레임 워크에서 사용합니다.
ViewModel은 View를 위한 Model이라고 할 수 있으며, View와 데이터 바인딩을 통해 연결됩니다.
일반적으로 View와 ViewModel은 옵저버 패턴을 사용하여, MVC 패턴의 View, Controller 간의 강한 결합을 해소합니다.
하지만 느슨한 결합으로 인해, 데이터 흐름을 추적하기 어려워지기 때문에 디버깅이 힘들어진다는 단점이 존재합니다.
📌 REST API에 대해 아는대로 설명하세요.
(저도 이거 다 못 외웁니다. 단골 질문인데, 매번 답변이 바뀌는 매직 🪄)
REST는 REpresentational State Transfer의 약자로, 이를 해석하면 네트워크 상태가 전이되는 것에 대한 표현입니다. (실제 정의은 "분산 하이퍼미디어 시스템을 위한 아키텍처")
이는 웹의 기존 기술과 HTTP 프로토콜을 그대로 활용하여, 웹의 장점을 최대한 활용하기 위한 아키텍처입니다.
REST API는 이를 표현하기 위해, Resource, Method, Representation 3가지를 사용하여 상태가 전이되는 것을 표현합니다.
- Resource: 자원, URI
- Method: HTTP Method
- Representation of Resource: 자원의 형태 (JSON, XML 등)
REST API의 주요 특징은 다음과 같습니다.
- 클라이언트 - 서버 구조
- 네트워크가 자원이 있는 서버와 자원을 요청하는 클라이언트로 구성됩니다.
- 클라이언트와 서버가 독립적으로 진화할 수 있습니다.
- 무상태성(Stateless)
- 모든 요청은 이전 요청과 독립적이어야 합니다.
- 서버와 클라이언트는 서로의 상태를 저장할 필요가 없습니다.
- Cacheable
- 서버의 응답은 캐시 가능 여부를 명시해야 합니다.
- `cache-control` 헤더를 사용합니다.
- Uniform Inteface
- 자원에 대한 요청을 통일하고 한정적인 인터페이스로 수행하는 규칙입니다.
- Resource는 URI로 식별할 수 있어야 합니다.
- Resource에 대한 행위(Verv)는 HTTP Method로 수행합니다.
- Representation 전송을 통해 Resource를 조작해야 합니다.
- 클라이언트가 자원(URI)의 상태에 대한 조작을 요청하면, 서버가 알맞게 응답(Representation of Resource)해야 합니다.
- 메시지는 스스로를 설명할 수 있어야 합니다.
- (HATEOAS) 애플리케이션의 state는 Hyperlink를 이용해 전송되어야 합니다. (서버가 제공하는 링크를 통해 클라이언트가 동적으로 다음 작업을 수행하는 것)
- HTTP 표준을 따른다면, 모든 플랫폼에서 사용 가능합니다.
- 자원에 대한 요청을 통일하고 한정적인 인터페이스로 수행하는 규칙입니다.
- Layered System
- 서버는 계층화된 시스템 아키텍처를 사용할 수 있습니다. (Proxy처럼 서버를 계층적으로 구성할 수 있음.)
- 그러나 각 레이어는 바로 다음 레이어만 알 수 있으며, 상호작용할 수 있습니다. (중간 계층이 요청이나 응답에 영향을 받아서는 안 된다.)
- 클라이언트는 최종 서버에 연결되어 있는지, 중간에 연결되어 있는지 알 수 없어야 합니다.
- Code-on-Demand (선택 사항)
- 서버가 실행 코드를 클라이언트에 보낼 수 있습니다.
- 클라이언트가 서버에 코드를 요청할 수 있으면, 요청을 받은 서버는 실행 가능항 코드(로직)을 제공합니다.
🟡 RESTful의 조건은 무엇인가요?
위의 REST 제약 조건을 모두 만족하는 시스템을 "RESTful"하다고 할 수 있습니다.
RESTful을 준수하면, 분산 시스템 간의 통신을 효율적이고 표준화된 방식으로 설계할 수 있습니다.
하지만 실제로 모든 조건을 완벽히 만족시키기는 어렵습니다.
특히 Uniform Interface(인터페이스 일관성)으로 인해, 많은 API가 RESTful 제약 조건을 지키기 어려워합니다.
🟡 GraphQL은 뭐고, REST API와의 차이는 무엇인가요?
GraphQL은 페이스북이 개발한 쿼리 언어이자 API를 위한 런타임입니다.
REST API가 여러 엔드포인트를 통해 데이터를 제공하는 것과 달리, GraphQL은 단일 엔드포인트에서 클라이언트가 필요한 데이터를 정확히 요청할 수 있게 해줍니다.
// REST API
GET /api/users/123 // 사용자 정보
GET /api/users/123/posts // 사용자의 게시글
// GraphQL
query {
user(id: "123") {
name
email
posts {
title
content
}
}
}
GraphQL을 사용하면 유연한 데이터 요청과 효율적인 네트워크 사용이 가능합니다.
특히 모바일 애플리케이션처럼 네트워크 효율성이 중요한 경우, GraphQL이 유리할 수 있습니다.
🟡 GraphQL의 단점은 무엇인가요?
(딱 한 번 여기까지 질문을 받아본 적이 있다.)
첫째, 학습 곡선이 상당히 가파릅니다.
개발팀은 새로운 쿼리 언어와 스키마 정의를 학습해야 하며, 기존의 REST API 개발 경험을 GraphQL에 맞게 전환할 필요가 있습니다.
특히 기존 시스템을 마이그레이션 해야 하는 경우, 복잡한 스키마 설계와 관계 정의는 많은 시행착오를 동반할 수 있습니다.
둘째, 캐싱 구현이 복잡합니다.
REST API는 URL 기반의 HTTP 캐싱을 자연스럽게 활용할 수 있습니다.
하지만 GraphQL은 단일 엔드포인트만을 사용하기 때문에, 이러한 표준 캐싱 메커니즘을 사용할 수 없습니다.
따라서 클라이언트 측에서 새로운 라이브러리를 도입하거나, 직접 캐싱 로직을 구현해야 합니다.
셋째, 파일 업로드와 같은 일부 기능 구현이 REST에 비해 복잡합니다.
GraphQL 스펙 자체에는 파일 업로드 방식이 정의되어 있지 않습니다.
이를 위한 추가적인 라이브러리 도입이나 구현을 필요로 합니다.
넷째, 성능 문제가 발생할 수 있습니다.
클라이언트가 매우 복잡한 중첩 쿼리를 요청할 경우, 서버에 과도한 부하가 걸릴 수도 있습니다.
이런 경우 N+1 문제나 과도한 Join 등이 발생할 수 있기 때문에, 추가적인 최적화 도구를 사용해야 합니다.
마지막으로, 모니터링과 에러 추적이 어려울 수 있습니다.
단일 엔드포인트에서 다양한 작업이 처리되기 때문에, 특정 문제의 원인을 파악하고 디버깅하는 것이 REST API에 비해 복잡할 수 있습니다.
📌 HTTP 메서드에 대해 설명하세요.
HTTP 메서드란 클라이언트와 서버 사이에 이루어지는 요청과 응답 데이터를 전송하는 방식을 말합니다.
쉽게 말해, 서버가 수행해야 할 동작을 요청으로 보내는 방법입니다.
표준 HTTP 메서드 종류는 총 9가지가 있습니다.
- GET: 리소스 조회
- POST: 주로 리소스를 생성하기 위해 사용
- PUT: 리소스를 덮어쓰기로 대체하고, 없으면 생성
- PATCH: 리소스 부분 변경
- DELETE: 리소스 삭제
- HEAD: GET과 동일하지만 메시지 부분을 제외하고, 상태 줄과 헤더만 반환
- OPTIONS: 대상 리소스에 대한 통신 가능 옵션을 설명
- CONNECT: 대상 자원으로 식별되는 서버에 대한 터널을 설정
- TRACE: 대상 리소스에 대한 경로를 따라 메시지 루프백 테스트를 수행 (보안상 이유로 거의 사용 안 한다고 함)
(HTTP 메서드는 웬만하면 HEAD, CONNECT, TRACE를 제외하고 자세히 공부해두는 것이 좋다. 정말 다양하게 꼬리 질문이 들어온다. 근데 요새 주요 메서드는 거의 다 아니까, 자꾸 HEAD 이런 것도 물어보긴 한다.)
🟡 HTTP 메서드의 멱등성에 대해 설명해주세요.
HTTP 메서드 속성에는 안전(safe), 캐시 가능성(cacheable)과 함께 멱등성(idempotence)이 있습니다.
멱등성이란 여러 번 동일한 요청을 보냈을 때, 서버에 미치는 의도된 영향이 동일해야 한다는 성질입니다.
그 자체로 멱등성을 지키는 메서드로는 GET, HEAD, PUT, DELETE가 존재합니다.
HTTP 멱등성이 필요한 이유는 요청의 재시도에서 비롯하는데, 요청이 실패하거나, 처리 도중에 클라이언트가 계속해서 요청을 보내는 경우가 있습니다.
GET, HEAD처럼 자원을 조회하기만 하거나, DELETE 처럼 이미 삭제한 자원을 한 번 더 삭제할 수 없는 경우, 별도의 조치 없이 멱등성을 보장할 수 있습니다.
반면, POST 요청은 같은 요청을 N번 호출하면, N개의 새로운 리소스가 생성되거나, 상태가 변경되면서 호출 결과가 달라질 수 있습니다.
PATCH의 경우 기존 리소스의 값의 변경이 아닌, 값을 추가하기 위해 사용하는 경우 멱등성이 깨질 수 있습니다.
(아주 가끔 특정 사례를 들면서, 이건 멱등성이 깨진 경우냐 아니냐를 물어볼 때도 있다. 멱등성은 구현에 따른 부작용이나, 응답 상태 코드, 외부 요청에 의한 변경 등이 아닌 사용자 동일 요청에 의한 최종 리소스 상태만을 고려한다는 걸 기억하면 쉽게 답할 수 있다.)
🟡 클라이언트의 요청을 기반으로 데이터를 수정하거나, 없으면 생성하려면 어떤 HTTP Method를 사용하나요?
PUT 메서드가 가장 적절합니다.
POST는 자원을 새로 생성하기 위해 사용하고, PATCH는 수정할 대상이 있어야만 작업을 수행합니다.
반면, PUT 메서드는 자원이 없으면 생성하고, 있으면 수정하는 동작을 명시하고 있습니다.
🟡 GET 요청에서 body를 사용하는 것을 지양하는 이유가 뭔가요?
GET 요청은 서버에서 리소스를 검색하기 위해 설계되었습니다.
그리고 HTTP 메서드 중 HEAD와 함께 안전(safe)하며, 멱등하며, 캐시 가능한 메서드에 해당합니다.
하지만 이를 보장하기 위해선, 쿼리는 되도록 URL의 쿼리 파라미터에 추가하는 것을 권장하고 있습니다.
그 이유는 대부분의 캐싱이나 프록시 시스템은 URL과 쿼리 파라미터만을 캐시 키로 사용하며, body 내용은 고려하지 않기 때문입니다.
그리고 HTTP 스펙상, GET 요청의 body를 가지면 서버가 이를 무시할 수 있습니다.
GET 요청에 body가 허용되기 시작한 것은 2014년 이후부터기 때문에, 이전의 시스템, 혹은 여전히 이를 거부하는 서버나 프록시에서 거부당할 수 있습니다.
그리고 URL의 완전성 측면에서도 문제가 될 수 있습니다.
GET의 요청의 모든 파라미터는 URL에 포함되어 있어야 요청의 의도를 정확히 파악하고, 북마크, 브라우저 기록, 공유 등을 수행할 수 있습니다.
body에 데이터를 숨기면 이러한 장점들을 활용할 수 없게 됩니다.
(안전과 멱등에 대한 이야기도 나오는데, 이는 실제로 깨진다기보단 깨질 수 있다는 "인식"을 줄 수 있기 때문에 문제되는 내용.)
🟡 조회할 때 POST를 지양하는 이유가 무엇인가요?
첫째, POST 요청은 기본적으로 non-cacheable합니다..
GET은 브라우저나 중간 프록세 서버에서 자동으로 캐싱이 가능하지만, POST 요청은 그렇지 않습니다.
따라서 매번 서버에게 새롭게 응답을 받아야 합니다.
둘째, 브라우저가 동작을 오인할 수 있습니다.
POST 요청은 브라우저에서 새로고침을 할 때, "양식 다시 제출" 경고를 발생시킬 우려가 존재합니다.
이는 사용자 경험을 해치고, 실수로 같은 데이터가 여러 번 서버로 전송될 수 있습니다.
셋째, HTTP 규칙을 위배합니다.
HTTP에서 POST는 새로운 리소스를 생성하는 용도로 사용해야 하며, 조회는 GET을 사용하는 것이 의미상 더 명확합니다.
🟡 안전한 메서드와 그렇지 않은 메서드의 차이는 무엇인가요?
HTTP 메서드에서 안전(safe)하다는 말의 정의는 메서드를 여러번 호출해도 리소스가 변경되지 않는 성질을 이야기합니다.
즉, 메서드를 호출해도 서버의 리소스를 변경하거나, 사이드 이펙트를 일으키지 않기 때문입니다.
GET, OPTION, HEAD, TRACE는 기본적으로 데이터 조회, 혹은 임의의 정보를 알아내기 위한 메서드일 뿐, 자원의 상태를 변경하는 의미를 내포하고 있지 않기 때문입니다.
반면, POST, PUT, DELETE, PATCH는 서버의 리소스를 변경할 수 있기 때문에 안전하지 않은 메서드입니다.
단, 여기서 GET 요청이 서버에 로그를 남기거나, 접근 카운터를 증가시키는 것은 안전하지 않다고 보지 않습니다.
왜냐하면, HTTP 메서드에서 안전이란 URI로 명시한 자원에 대한 상태를 의미하지, 서버의 구현 사항에 대한 이야기가 아니기 때문입니다.
🟡 HTTP 캐시와 관련하여 각 메서드의 특징을 설명해주세요.
🟡 서버가 지원하는 HTTP 메서드를 확인하기 위한 HTTP 메서드는 무엇인가요?
🟡
📌 HTTP 상태 코드에 대해 아는 대로 설명하세요.
🟡 만약 요청의 결과로 자원이 생성되거나, 수정될 수 있을 때는 어떤 메서드가 적절한가요?
📌 URL과 URI의 차이에 대해 설명하세요.
📌 객체 지향 원칙에 대해 설명하세요.
🟡 객체 지향 설계 원칙 5가지에 대해 아는대로 설명하세요.
📌 절차 지향 프로그래밍, 객체 지향 프로그래밍, 함수형 프로그래밍에 대해 아는 대로 설명하세요.
📌 Call by Value와 Call by Reference의 차이에 대해 설명하세요.
🟡 (자바 개발자) Java에서는 어느 부분이 Call by Value고, 어느 부분이 Call by Refrerence인가요?
📌 프레임워크와 라이브러리의 차이에 대해 설명하세요.
📌 메시지 큐에 대해 설명하세요.
📌 새로운 라이브러리를 프로젝트 내에 도입하려고 할 때, 라이브러리가 예상대로 동작함을 검증하려면 어떻게 해야 하나요?
🟡 TDD에 대해 설명해주세요.
🟡 단위 테스트와 통합 테스트는 어떻게 구분하시나요?
🟡 Mock과 Stub의 차이점은 무엇인가요?
🟡 테스트 커버리지를 높이기 위한 전략은 무엇인가요?
🟡 테스트 코드를 작성해야 하는 이유에 대해 아는 대로 설명해주세요.
📌 DDD에 대해 아는 대로 설명하세요.
📌 모놀리식 아키텍처와 MSA에 대해 아는 대로 설명하세요.
📌 이벤트 주도 아키텍처(Event-Driven Architecture)에 대해 아는 대로 설명하세요.
📌 VO와 BO, DAO, DTO에 대해 설명하세요.
📌 CORS에 대해 설명하세요.
📌 CI/CD에 대해 설명하세요.
🟡 CI/CD 파이프라인을 구축해본 경험이 있나요? 어떤 작업을 자동화하고, 어떤 도구를 사용했나요? 그에 대한 유의미한 성과가 있었나요?
📌 XSS와 CSRF 공격에 대해 설명하세요.
🟡 XSS 공격의 종류는 어떤 것들이 있고, 어떻게 막을 수 있나요?
🟡 CSRF 공격의 종류는 어떤 것들이 있고, 어떻게 막을 수 있나요?
🟡 (백엔드) Spring Security에서는 이러한 공격들을 어떻게 방어하나요?
🟡 CSP(Content Security Policy)는 어떤 역할을 하나요?
📌 OAuth 2.0에 대해 설명하고 간략한 플로우를 이야기하세요.
🟡 Authorization Code Grant Type과 다른 Grant Type들의 차이를 아시나요?
🟡 OIDC(Open ID Connection) 인증 방식에 대해 설명해주세요.
📌 JWT에 대해 설명해주세요.
📌 아스키코드와 유니코드, EUC-KR과 UTF-8의 차이에 대해 설명하세요.
📌 Docker를 왜 사용하나요?
🟡 Docker의 작동 원리에 대해 아는 대로 설명해주세요.
📌 UI/UX의 차이를 간략하게 설명해주세요.
(진짜 질문 받은 적이 있음. 아마도 PM 경력 때문인 듯.)