책 소개
소스 파일은 아래 깃허브 페이지에서 내려 받으실 수 있습니다.
(https://github.com/AcornPublishing/seriously-software/)
요약
프로그래밍 언어의 기본을 익힌 독자를 대상으로 하는 책으로, 소프트웨어의 품질을 결정짓는 여러 가지 기준과 그 기준에 부합하는 소프트웨어를 개발하는 방법을 예제 바탕으로 설명한다. 더 나아가, 품질 기준 사이에 필연적인 충돌이 있음을 이해하고, 이러한 기회비용을 어떻게 다뤄야 할지 생각해볼 기회를 제공한다.
이 책에 쏟아진 찬사
“더 나은 소프트웨어를 만드는 실용적인 지침서”
“모든 컴퓨터과학 전공자가 꼭 봐야 할 책!”
“모든 회사의 신입 개발자가 꼭 읽어야 할 책이다.”
추천의 글
지난 30년 동안 프로그래밍 서적 몇 권을 저술한 덕분에 책을 쓰는 일에 관한 조언을 요청받곤 한다. 그때마다 우선 한 장(chapter)을 완성해 보여달라고 요청하는데 대부분 그 후로 소식이 없다. 한 장도 쓰지 못한다면 책 한 권은 완성될 수 없고 더 이상 할 얘기도 없기 마련이다.
2018년 2월 네이플대 마르코 파엘라 교수로부터 이메일을 받았다. 그가 캘리포니아대에서 일하던 시절 만난 적이 있었다. 그는 책 저술에 대한 조언을 구했는데 이미 몇 장이 완성된 상태였다! 살펴본 내용이 마음에 들어 몇 가지 조언과 격려를 담아 답장을 보냈다. 그 후 한동안 연락이 없었지만 놀랍지는 않았다. 알고 지내던 한 편집자가 말했듯이 저술을 시작하는 사람은 매우 많지만 저술을 끝마치는 사람은 드물기 때문이다.
2019년 4월 매닝출판사로부터 그의 책이 출간된다는 이메일을 받았는데 정말 훌륭한 책이었다. 8월에는 그가 내게 머리말을 부탁해 흔쾌히 승낙했다. (코어 자바 같은 고전적인)
프로그래밍 언어 관련 책을 쓸 때는 언어의 구성 요소와 특화된 API에 집중한다. 그리고 독자가 자료 구조와 알고리즘, (테스팅과 리팩토링, 디자인 패턴을 비롯한) 소프트웨어 공학 이론을 한 번쯤 접했다고 가정한다. 하지만 교수가 된 후 대학 교육 과정이 이러한 주제를 실용적이고 쉽게 가르치지 못한다는 사실을 깨달았다.
이 책은 그러한 부족함을 채워준다. 자바 프로그래밍의 기본에만 익숙하다면 저자가 고품질 프로그램을 향한 길을 제시할 것이다. 여러분이 알고리즘 설계, API 설계, 테스팅, 동시성에 경험이 있을 수 있지만 저자는 고전적인 주제에 새로운 관점을 제시한다.
저자는 한 예제를 다른 방식으로 계속 구현하면서 엄청나게 많은 통찰을 끌어낸다. 나는 이렇게 ‘실제로 돌아가는 예제’를 가지고 설명하는 방식을 좋아하지 않는다. 예제 프로그램이 진화하는 과정을 파악하기 위해 책을 꼭 순서대로 읽어야 하기 때문이다. 하지만 이 책의 예제는 영리하게 설계됐다. 놀랍고 흥미로운 핵심 개념 몇 가지를 첫눈에 이해한 후 각 장에서 독립적인 방향으로 코드를 발전시킨 나간다. 이는 놀라운 경험이 될 것이다.
중요한 내용을 다루는 장을 읽은 후 ‘이전과 완전하게 다른 뭔가’를 발견할 것이다. 그랬다면 새로운 기술을 다른 상황에 적용할 차례다. 본문 중간의 퀴즈와 각 장 끝부분의 연습문제를 꼭 풀어보길 바란다.
고품질 소프트웨어를 만드는 일은 결코 간단하지 않다. 하지만 훌륭한 설계 이론과 장인 정신을 다시 되새기는 것은 항상 도움을 준다. 내가 그랬듯이 여러분도 이 책에서 제시하는 신선한 관점을 즐겨보길 바란다.
초보와 숙련된 프로그래머를 위한 수많은 책(『Core Java』 시리즈, 『Modern JavaScript for the Impatient』 등)의 저자
이 책에서 다루는 내용
◆ 소프트웨어 품질 평가
◆ 소프트웨어 품질 기준의 기회 비용과 상호작용
◆ 한 가지 예제를 바탕으로 다양한 목표 달성
◆ 어떠한 객체지향 언어에도 적용할 수 있는 자바 기반 예제
이 책의 목표와 대상 독자
공식적인 교육을 받지 못한 주니어 개발자가 소프트웨어 개발의 시야를 넓히는 시작점으로 적합하다.
◆ 공식적인 교육이 부족하거나 컴퓨터과학, 컴퓨터공학 외의 다른 분야를 전공한 현업 개발자는 이 책을 통해 컴퓨터과학과 컴퓨터공학의 기법을 훑어보고 실제 프로그래밍 과업에서 근본적으로 상충하는 가치를 살펴볼 수 있다.
◆ 컴퓨터과학과 컴퓨터공학 전공생에게는 별도 강의에서 배운 다양한 주제를 통합하는 사례를 제공한다. 프로그래밍과 소프트웨어 공학 교과서의 보충 교재로 사용할 수 있다.
이 책의 구성
각 장에서 어떠한 관점의 코드 품질을 다루는지 다음과 같이 요약했다. 각 장 끝부분의 연습문제를 그냥 지나치지 말자. 상세한 해답을 제공하며 해당 장에서 다룬 기법을 다른 상황에 적용해 핵심 개념을 완성해 나갈 수 있다.
1장에서는 프로그래밍 예제(수조 클래스)를 설명하고 경험이 부족한 프로그래머에게 나쁜 영향을 미치는 일반적인 오해를 보여주는 사례를 살펴본다.
2장에서는 여러 가지 관점에서 균형 잡힌 품질을 제공하는 레퍼런스 구현을 자세하게 살펴본다.
3장에서는 시간 효율성에 초점을 맞춰 레퍼런스 실행 시간을 수백 배(500배) 줄여본다. 상황에 따라 여러 가지 성능 기준 사이에 충돌이 발생하는 것을 설명한다.
4장에서는 공간(메모리) 효율성을 실험한다. 객체를 이용해 레퍼런스 대비 50%의 메모리 사용량을 절감한다. 그리고 수조를 표현하는 데 ‘객체’를 사용하지 않음으로써 90%의 메모리를 절감한다.
5장에서는 모니터링을 기반으로 신뢰성을 보장하기 위해 규약에 따른 설계방법론을 소개하고 메서드 규약과 클래스 불변 조건에 기반한 런타임 체크와 어서션을 이용해 레퍼런스를 견고하게 만드는 방법을 살펴본다.
6장에서는 단위 테스트로 신뢰성을 향상한다. 코드 커버리지 측정을 비롯한 각종 도구를 이용해 클래스의 테스트 슈트를 설계하고 실행하는 기법을 알아본다.
7장에서는 가독성을 다룬다. 명료한 자기설명적 코드의 모범 사례를 따라 레퍼런스를 리팩토링한다.
8장에서는 동시성과 스레드 안전성을 살펴본다. 스레드 동기화와 관련 있는 기본적인 개념을 되새겨보고 예제를 바탕으로 교착 상태와 경합 조건을 피하는 데 복잡한 기법이 필요하다는 것을 확인한다.
9장에서는 재사용성에 집중한다. 유사한 구조를 지닌 다른 문제에 레퍼런스 클래스를 활용할 수 있게 제네릭을 이용한 일반화를 실습한다.
부록 A에서는 간결함을 주제로, 레퍼런스 코드 길이의 15% 정도로 짧은 구현 방식을 살펴본다.
부록 B에서는 가장 중요한 소프트웨어 품질 기준을 통합해 궁극의 수조 클래스를 작성한다.
목차
목차
- 1부. 먼저 알아야 할 것들
- 1장. 소프트웨어 품질과 앞으로 풀어야 할 문제
- 1.1 소프트웨어 품질
- 1.1.1 내적 품질과 외적 품질
- 1.1.2 기능적 품질과 비기능적 품질
- 1.2 외적 소프트웨어 품질에 가까운 기준들
- 1.2.1 정확성
- 1.2.2 견고성
- 1.2.3 사용성
- 1.2.4 효율성
- 1.3 내적 소프트웨어 품질에 가까운 기준들
- 1.3.1 가독성
- 1.3.3 테스트 용이성
- 1.3.4 유지보수성
- 1.4 소프트웨어 품질 기준 사이의 상호작용
- 1.5 특수한 품질
- 1.5.1 스레드 안전성
- 1.5.2 간결성
- 1.6 앞으로 살펴볼 예제: 수조 시스템
- 1.6.1 API
- 1.6.2 활용 사례
- 1.7 데이터 모델과 데이터 표현
- 1.7.1 물의 양 저장하기
- 1.7.2 연결 상태 저장하기
- 1.8 Hello containers! [Novice]
- 1.8.1 필드와 생성자
- 1.8.2 getAmount와 addWater 메서드
- 1.8.3 connectTo 메서드
- 요약
- 더 읽을거리
- 2장. 레퍼런스 구현
- 2.1 레퍼런스 코드 [Reference]
- 2.1.1 메모리 레이아웃 다이어그램
- 2.1.2 메서드
- 2.2 메모리 요구 사항
- 2.2.1 Reference의 메모리 요구량
- 2.3 시간 복잡도
- 2.3.1 Reference의 시간 복잡도
- 2.4 배운 내용 연습하기
- 요약
- 퀴즈와 연습 문제 정답
- 더 읽을거리
- 2부. 소프트웨어 품질
- 3장. 질주 본능: 시간 효율성
- 3.1 상수 시간에 물 넣기 [Speed1]
- 3.1.1 시간 복잡도
- 3.2 상수 시간에 연결 추가하기 [Speed2]
- 3.2.1 순환 리스트로 그룹 표현하기
- 3.3.2 갱신 지연
- 3.3 최적의 균형: 합집합 찾기 알고리즘 [Speed3]
- 3.3.1 그룹 대표 찾기
- 3.3.2 수조의 트리 연결하기
- 3.3.3 최악의 경우의 시간 복잡도
- 3.3.4 분할상환 시간 복잡도
- 3.3.5 크기가 변하는 배열을 이용할 경우의 분할상환 분석
- 3.4 구현 방식 비교하기
- 3.4.1 실험
- 3.4.2 이론 대 실제
- 3.5 전혀 새로운 문제에 적용해보기
- 3.5.1 빠른 삽입
- 3.5.2 빠른 조회
- 3.5.3 모든 것을 빠르게
- 3.6 실제 사례
- 3.7 배운 내용 적용해보기
- 요약
- 퀴즈와 연습문제 해답
- 더 읽을거리
- 4장. 소중한 메모리: 공간 효율성
- 4.1 검소한 버전 [Memory1]
- 4.1.1 공간 및 시간 복잡도
- 4.2 일반 배열 [Memory2]
- 4.2.1 공간 및 시간 복잡도
- 4.3 객체여, 안녕 [Memory3]
- 4.3.1 객체를 사용하지 않는 API
- 4.3.2 필드와 getAmount 메서드
- 4.3.3 팩토리 메서드로 수조 생성하기
- 4.3.4 ID로 수조 연결하기
- 4.3.5 공간 및 시간 복잡도
- 4.4 블랙홀 [Memory4]
- 4.4.1 공간 및 시간 복잡도
- 4.5 공간 - 시간 기회비용
- 4.6 전혀 새로운 문제에 적용해보기
- 4.6.1 중복이 적을 때
- 4.6.2 중복이 많을 때
- 4.7 실제 사례
- 4.8 배운 내용 적용해보기
- 요약
- 퀴즈와 연습문제 해답
- 더 읽을거리
- 5장. 모니터링을 이용한 신뢰성 향상
- 5.1 계약에 의한 설계
- 5.1.1 사전 조건과 사후 조건
- 5.1.2 불변 조건
- 5.1.3 정확성과 견고성
- 5.1.4 계약 검사
- 5.1.5 더 큰 그림
- 5.2 계약을 바탕으로 수조 설계하기
- 5.3 계약을 검사하는 수조 [Contracts]
- 5.3.1 addWater의 계약 검사하기
- 5.3.2 connectTo의 계약 검사하기
- 5.4 불변 조건을 검사하는 수조 [Invariants]
- 5.4.1 connectTo에서 불변 조건 검사하기
- 5.4.2 addWater 불변 조건 검사
- 5.5 전혀 새로운 문제에 적용해보기
- 5.5.1 계약
- 5.5.2 기본 구현
- 5.5.3 계약 검사
- 5.5.4 불변 조건 검사
- 5.6 실제 사례
- 5.7 배운 내용 적용해보기
- 요약
- 퀴즈와 연습문제 해답
- 더 읽을거리
- 6장. 나를 속여봐: 테스트를 이용한 신뢰성 향상
- 6.1 테스트의 기본적 개념
- 6.1.1 테스트 커버리지
- 6.1.2 테스트와 계약에 의한 설계
- 6.1.3 JUnit
- 6.2 수조 테스트하기 [UnitTests]
- 6.2.1 테스트 초기화하기
- 6.2.2 addWater 테스트하기
- 6.2.3 connectTo 테스트하기
- 6.2.4 테스트 수행
- 6.2.5 커버리지 측정
- 6.3 테스트 용이성 [Testable]
- 6.3.1 제어 가능성
- 6.3.2 관측 가능성
- 6.3.3 고립하기: 의존성 제공
- 6.4 전혀 새로운 문제에 적용해보기
- 6.4.1 테스트 용이성 개선
- 6.4.2 테스트 슈트
- 6.5 실제 사례
- 6.6 배운 내용 적용해보기
- 요약
- 퀴즈와 연습문제 해답
- 더 읽을거리
- 7장. 큰소리로 코딩하자: 가독성
- 7.1 가독성을 바라보는 관점
- 7.1.1 기업 코딩 스타일 가이드
- 7.1.2 가독성의 재료
- 7.2 구조적 가독성 특징
- 7.2.1 제어 흐름 구문
- 7.2.2 표현식과 지역 변수
- 7.3 외부적 가독성 특징
- 7.3.1 주석
- 7.3.2 이름 짓기
- 7.3.3 공백과 들여쓰기
- 7.4 가독성 높은 수조 [Readable]
- 7.4.1 자바독을 이용한 클래스 헤더 문서화
- 7.4.2 connectTo 정리하기
- 7.4.3 addWater 정리하기
- 7.5 가독성에 대한 마지막 고찰
- 7.6 전혀 새로운 문제에 적용해보기
- 7.7 실제 사례
- 7.8 배운 내용 적용해보기
- 요약
- 퀴즈와 연습문제 해답
- 더 읽을거리
- 8장. 스레드 안전성
- 8.1 스레드 안전성 달성의 어려움
- 8.1.1 동시성 레벨
- 8.1.2 수조 시스템의 동시성 정책
- 8.2 교착 상태 다루기
- 8.2.1 원자적 락 시퀀스
- 8.2.2 순서 있는 락 시퀀스
- 8.2.3 숨은 경합 조건
- 8.3 스레드 안전한 수조 [ThreadSafe]
- 8.3.1 connectTo 동기화
- 8.3.2 addWater와 getAmount 동기화
- 8.4 불변성 [Immutable]
- 8.4.1 API
- 8.4.2 구현
- 8.5 전혀 새로운 문제에 적용해보기
- 8.6 실제 사례
- 8.7 배운 내용 적용해보기
- 요약
- 퀴즈와 연습문제 해답
- 더 읽을거리
- 9장. 재활용합시다: 재사용성
- 9.1 경계 찾기
- 9.2 일반적인 프레임워크
- 9.2.1 속성 관리 API
- 9.2.2 가변 컬렉터
- 9.2.3 함수형 인터페이스를 컨테이너 속성에 적용하기
- 9.3 제네릭 컨테이너 구현
- 9.4 일반적인 고려사항
- 9.5 수조의 기능 재현하기 [Generic]
- 9.5.1 수정된 시나리오
- 9.5.2 구체적인 속성 설계
- 9.5.3 구체적인 수조 클래스 정의
- 9.6 소셜 네트워크 포스트
- 9.7 전혀 새로운 문제에 적용해보기
- 9.7.1 파라미터화된 함수를 표현하는 인터페이스
- 9.7.2 통신 방식 개선
- 9.8 실제 사례
- 9.9 배운 내용 적용해보기
- 요약
- 퀴즈와 연습문제 해답
- 더 읽을거리
- 부록 A. 코드 골프: 간결성
- 부록 B. 궁극의 수조 클래스