├── .gitignore ├── .idea └── .gitignore ├── LICENSE ├── README.md ├── book_600.png ├── converter.png └── src └── main └── java └── net └── mingleup └── guide ├── MainApp.java └── employee ├── EmployeePaySwitch.java ├── EmployeePaySwitchRefactor.java ├── abstractfactory ├── AssistPayCalculator.java ├── ContractPayCalculator.java ├── EmployeePayAbstractFatoryMain.java ├── PayCalculator.java ├── PayCalculatorFactory.java ├── RegularPayCalculator.java └── TemporaryPayCalculator.java └── strategy ├── AssistPayCalculator.java ├── ContractPayCalculator.java ├── EmployeePayStrategyMain.java ├── PayCalculator.java ├── PayCalculatorFactory.java ├── RegularPayCalculator.java └── TemporaryPayCalculator.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | replay_pid* 25 | 26 | *.iml 27 | jpa-buddy.xml 28 | *.idea -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Coding Guide for Java Programmer By 코드빌런 2 | 3 | > 본 내용은 한빛미디어에서 출간한 제 책(개발자 기술 면접 노트)의 일부 내용을 보강하기 위해서 만든 코딩 가이드 입니다.
4 | > 설명에서는 타이핑을 줄이기 위해서 존대를 생략했으니 양해 바랍니다.
5 | > 만약 내용에 문제가 있거나 오/탈자가 있을 경우 villainscode@gmail.com으로 메일 부탁드립니다. 6 | > 7 | > 8 | > - Instagram - [https://www.instagram.com/codevillains](https://www.instagram.com/codevillains) 9 | > - email - [villainscode@gmail.com](mailto:villainscode@gmail.com) 10 | > - Yes24 - https://www.yes24.com/Product/Goods/125554439 11 | > - KyoboBooks - https://product.kyobobook.co.kr/detail/S000212738756 12 | 13 | > 14 | > 개정 이력 15 | > 16 | > 현재 버전 v.1.0.0 (2024.05.09) 17 | > - 더 다양한 코드 용례를 추가할 때 마다 개정 이력은 업데이트 될 예정입니다. 18 | > 19 | 20 | - [목차](#--) 21 | - [코딩 가이드](#---------) 22 | * [들어가기에 앞서](#--------) 23 | * [소개](#--) 24 | - [코딩 가이드 개요](#---------) 25 | - [일반적인 자바 코딩 규칙](#-------------) 26 | * [기본적인 용어](#-------) 27 | * [클래스와 객체, 객체와 인스턴스](#-----------------) 28 | * [메서드와 함수](#-------) 29 | * [나쁜 코드](#-----) 30 | * [좋은 코드를 작성하는 몇가지 규칙들](#-------------------) 31 | - [의미 있는 이름](#--------) 32 | + [1. 그릇된 정보를 피해라.](#1------------) 33 | + [2. 의미있게 구분하라.](#2----------) 34 | + [3. 검색하기 쉬운 이름, 발음하기 쉬운 이름을 사용하라.](#3-----------------------------) 35 | + [4. 클래스 이름 규칙](#4----------) 36 | + [5. 메서드 이름 규칙](#5----------) 37 | + [6. 한 개념에 한 단어를 사용하라.](#6-----------------) 38 | - [함수 (메서드)](#--------) 39 | + [1. switch 문](#1-switch--) 40 | + [2. 서술적인 이름을 사용하라.](#2--------------) 41 | + [3. 메서드 인수](#3-------) 42 | + [4. 명령과 조회를 분리해라.](#4-------------) 43 | + [5. DRY (Don't Repaet Yourself)](#5-dry--don-t-repaet-yourself-) 44 | + [6. 주석은 코드로 대체할 수 있어야 한다.](#6---------------------) 45 | - [형식 맞추기](#------) 46 | - [객체와 자료 구조](#---------) 47 | + [디미터의 법칙 : 오직 하나의 점(.)에 의해 호출되는 메서드만 호출해야 한다.](#------------------------------------------) 48 | + [자료 전달 객체](#--------) 49 | - [오류 처리](#-----) 50 | + [오류 코드보다 예외를 사용하라.](#----------------) 51 | + [null을 반환하지 마라.](#null---------) 52 | - [클래스](#---) 53 | + [클래스 작성 규칙](#---------) 54 | + [클래스는 작게 설계 해야한다.](#---------------) 55 | + [SRP](#srp) 56 | + [응집도(Cohesion)](#----cohesion-) 57 | - [코드 기본 사용 규칙 모음](#--------------) 58 | + [규칙 1. 파일은 UTF-8로 인코딩 한다.](#---1-----utf-8--------) 59 | + [규칙 2. 패키지 이름은 소문자이고, 언더라인을 쓰지 않는다.](#---2----------------------------) 60 | + [규칙 3. 클래스 이름은 UpperCamelCase (PascalCase) 로 작성한다. ](#---3---------uppercamelcase--pascalcase---------------------------------------) 61 | + [규칙 4 .메소드 이름은 lowerCamelCase로 작성한다.](#---4---------lowercamelcase-------------------------) 62 | + [규칙 5. 너무 짧은 변수명, 메소드명은 지양하고 의미있는 이름으로 지어야 한다. ](#---5---------------------------------------------------------------------------) 63 | + [규칙 6. 상수는 CONST_CASE 이다. (UPPER_SNAKE_CASE)](#---6-----const-case-----upper-snake-case-) 64 | + [규칙 7. 상수가 아닌 필드는 lowerCamelCase로 작성한다.](#---7------------lowercamelcase------) 65 | + [규칙 8. 변수는 lowerCamelCase로 작성한다.](#---8-----lowercamelcase------) 66 | - [Usage (코드 예제 모음)](#usage-----------) 67 | + [1. 괄호는 Egyptian brackets 을 따른다. ](#1-------egyptian-brackets--------) 68 | + [2. 빈 블럭이나 block-like construct](#2----------block-like-construct---------------------------------------) 69 | + [3. 공백과 빈줄 ](#3-----------) 70 | + [4. Static Factory Method 의 관행적인 명명 ](#4---static-factory-method------------) 71 | + [5. List 타입에서 빈 컬렉션을 반환](#5---list-------------------null---new---------------empty-collection---------gc--------) 72 | + [6. foreach 루프에서 콜렉션의 요소를 추가하거나 삭제하지 말것 ](#6---foreach------------------------------) 73 | + [7. Key, Value 콜렉션에서 null이 저장되는지 체크해두어야 한다. ](#7---key--value-------null-------------------) 74 | + [8. Thread ](#8---thread--) 75 | + [9. String 타입에 적절하지 않은 데이터를 표기하지 말아야 한다. ](#9---string-------------------------------) 76 | + [10. String의 초기화에는 StringUtils.EMPTY을 사용하는 것을 권장한다. ](#10---string--------stringutilsempty----------------) 77 | + [11. 불필요한 연산은 사용하지 말자. ](#11---------------------) 78 | + [12. Optional 사용 ](#12---optional-----) 79 | + [13. Predicate과 Valdator 구분해서 사용 ](#13---predicate--valdator----------) 80 | + [14. 변수가 너무 많은 클래스 구조는 별도의 객체로 분리하자. ](#14-----------------------------------) 81 | + [15. 가급적 예외를 구체화 한다. ](#15-------------------) 82 | + [16. 짧은 try ~ catch 블럭 권장 ](#16------try---catch--------) 83 | + [17. SpringFramework 에서 제공하는 자체 Util 들은 사용하면 안된다. ](#17---springframework------------util--------------) 84 | + [18. Spring Annotation의 적절한 사용 ](#18---spring-annotation----------) 85 | - [DTO (Converter vs Mapper)](#dto--converter-vs-mapper-) 86 | 87 | Table of contents generated with markdown-toc 88 | 89 | 90 | # 📖 코딩 가이드 91 | 92 | ## 들어가기에 앞서 93 | 이 내용은 아래의 서적과 링크를 참고하여 작성하였다. 각종 예제 코드들은 코드빌런 본인의 개인코드를 포함하여 챗GPT의 도움을 받았다.GPT만쉐! 94 | 95 | ## 소개 96 | 97 | 98 | 99 | 100 | > 101 | > 참고 서적 및 링크 102 | > - Effective Java 3/E (Joshua Bloch) 103 | > - Clean Code (Robert C. Martin) 104 | > - [개발자 기술 면접 노트](https://github.com/villainscode) (Technical Interview Notes for Java Developers, Hanbit Media 2024.03.25, Code Villains) 105 | > - https://google.github.io/styleguide/javaguide.html 106 | > - https://www.oracle.com/java/technologies/javase/codeconventions-contents.html 107 | 108 | 109 | # 인프런 강의 110 | 111 | 112 | 113 | - [시니어면접관이 알려주는 개발자 취업과 이직, 한방에 해결하기 이론편](https://www.inflearn.com/course/%EC%8B%9C%EB%8B%88%EC%96%B4-%EB%A9%B4%EC%A0%91%EA%B4%80-%EC%95%8C%EB%A0%A4%EC%A3%BC%EB%8A%94-%EC%B7%A8%EC%97%85-%EC%9D%B4%EC%A7%81-%EC%9D%B4%EB%A1%A0) 114 | - [시니어면접관이 알려주는 개발자 취업과 이직, 한방에 해결하기 실전편](https://www.inflearn.com/course/%EC%8B%9C%EB%8B%88%EC%96%B4-%EB%A9%B4%EC%A0%91%EA%B4%80-%EC%95%8C%EB%A0%A4%EC%A3%BC%EB%8A%94-%EC%B7%A8%EC%97%85-%EC%9D%B4%EC%A7%81-%EC%8B%A4%EC%A0%84) 115 | 116 | 117 | 118 | # 코딩 가이드 개요 119 | 132 | 133 | # 일반적인 자바 코딩 규칙 134 | 135 | ## 기본적인 용어 136 | > 137 | > - Value Object : 비즈니스 용어를 나타내는 불변 객체 138 | > - Entity : 속성이 아닌 식별성을 기준으로 정의되는 도메인 객체, 여러 Value Object로 구성 139 | > - Service : 도메인 객체에 포함할 수 없는 동작, 로직적인 연산을 갖는 객체 140 | > - Aggregate : 연관된 Value Object와 Entity의 묶음 141 | > - Factory : 복잡한 Entity, Aggregate를 캡슐화하여 여러 객체를 동시에 생성 142 | > - Repository : Aggregate의 영속성 및 등록·수정·삭제·조회 시 일관성 관리 143 | 144 | ## 클래스와 객체, 객체와 인스턴스 145 | 146 | 클래스 : 객체를 생성하기 위한 설계 코드로 아래와 같은 형태를 가지고 있다. 147 | 148 | - 생성자 149 | - 속성 150 | - 메서드 151 | 152 | 구체적인 값을 가진 상태라기 보다는 추상적인 개념 153 | 154 | 객체 : 클래스를 통해 만들어진 구체적인 개체. 클래스에서 정의된 속성과 메서드를 가지고 데이터를 지닌 상태로 메서드를 호출하고 동작을 수행한다. 클래스에 정의된 메서드를 통해 객체의 속성과 행동을 이용하여 실제 프로그램에서 사용된다. 155 | 156 | 클래스 : 클래스는 건축물의 설계도와 같은 개념이고 객체는 설계도를 기반으로 만들어진 집과 같다.
157 | 클래스는 데이터와 그 데이터를 처리하는 메서드의 집합으로 이루어져 있다.
158 | 집이라는 클래스를 보면 방의 수, 크기, 화장실의 수, 외관의 색깔 등의 데이터를 가지고 집으로서의 필수적인 기능들을 수행하는 메서드를 가지고 있다.
159 | 160 | 인스턴스 : 인스턴스는 특정 클래스를 통해 실제로 메모리에 생성된 객체로 저마다 고유한 값(참조값)을 지니고 있다. 161 | 162 | 객체가 [집]이라고 하는 일반적인 개념이라면 인스턴스는 집 객체를 참조하는 [우리집, 영희네 집, 철수네 집] 이라고 생각할 수 있다.
163 | 각각의 집은 서로 다른 속성을 가질수 있지만 모두 집 클래스의 특성과 기능을 공유할 수 있다. 164 | 따라서 클래스와 객체는 1:N의 관계를 가지고, 객체와 인스턴스는 1:N의 관계를 가진다. 165 | 166 | 객체는 런타임(실행 시)에 메모리에 할당되는 반면, 클래스는 컴파일 시 메모리에 로드된다. 167 | 168 | ## 메서드와 함수 169 | 170 | 메서드는 특정 클래스에 속하며, 객체의 속성이나 행동을 정의하는 코드를 의미한다. 171 | 172 | 객체를 통해서만 호출할 수 있으며 객체간의 상호작용과 데이터 은닉을 구현한다. 173 | 174 | 함수는 좀 더 범용적인 개념으로 독립적으로 존재하며 특정 작업이나 기능을 수행하여 값을 반환하는, 자바 이외의 다른 프로그래밍에서도 사용되는 개념이다. 175 | 176 | 개념적으로 함수는 메서드보다 상위의 개념이지만, 주로 자바 진영에서는 초기부터 메서드라는 명명법이 익숙하기 때문에 함수 = 메서드 라고 표현을 할 것이다. 177 | 178 | ## 나쁜 코드 179 | 180 | 나쁜 코드가 쌓일 수록 팀 생산성은 떨어진다. 181 | 182 | 최대한 빠른 개발을 추구하는 가장 간단한 길은 최대한 코드를 클린하게 유지하는 것이다. 183 | 184 | 그러기 위한 몇가지 원칙은 아래와 같다. 185 | 186 | - 중복을 피하라. 187 | - 한기능만 수행하라 188 | - 제대로 표현하라 189 | - 작게 추상화하라. 190 | 191 | ## 좋은 코드를 작성하는 몇가지 규칙들 192 | 193 | 1. 보이스카우트 규칙 : 캠프장은 처음 왔을 때보다 더 깨끗하게 해놓고 떠나라. 194 | - 변수 이름을 명확히 하고 195 | - 조금 긴 메서드는 분할하고 196 | - 약간의 중복을 제거하고 197 | - 복잡한 if 문 하나를 정리하고… 198 | 2. 객체 지향 설계의 다섯가지 원칙 199 | - SRP(Single Responsibility Principle) : 클래스에는 한 가지 변경 이유만 존재해야 한다. 200 | - OCP(Open Closed Principle) : 클래스는 확장에 열려 있어야 하며 변경에 닫혀 있어야 한다. 201 | - LSP(Liskov Substitution Principle) : 상속받은 클래스는 기초 클래스를 대체할 수 있어야 한다. 202 | - DIP(Dependency Inversion Principle) : 추상화는 의존해야 하며, 구체화는 의존하면 안된다. 203 | - ISP(Interface Segregation Principle) : 클라이언트가 필요로 하는 기능만을 제공한다. (불필요한 기능들을 분리하여 인터페이스를 작게 유지해야 한다) 204 | 205 | # 의미 있는 이름 206 | 207 | 의도를 분명히 밝혀야 한다. 208 | 209 | ```java 210 | // 의미 없는 함수 이름 211 | public void processData(Object data) { 212 | // ... 213 | } // bad 214 | 215 | // 의미 있는 함수 이름 216 | public void calculateStudentGrade(List students) { 217 | // ... 218 | } // good 219 | ``` 220 | 221 | 조건의 경우 다음과 같은 코드가 더 명확히 읽힌다. 222 | 223 | 조건문 분리 코드 224 | 225 | ```java 226 | // 중첩 if 문 227 | if (user.isLogIn()) { 228 | if (user.isAdmin()) { 229 | // 관리자 페이지 표시 230 | } else { 231 | // 일반 사용자 페이지 표시 232 | } 233 | } else { 234 | // 로그인 페이지 표시 235 | } // bad 236 | ``` 237 | 238 | ```java 239 | // 조건문 분리 및 함수 사용 240 | if (user.isLogIn()) { 241 | renderUserPage(user); 242 | } else { 243 | renderLoginPage(); 244 | } 245 | 246 | private void renderUserPage(User user) { 247 | if (user.isAdmin()) { 248 | renderAdminPage(user); 249 | } else { 250 | renderUserPage(user); 251 | } 252 | } 253 | 254 | private void renderLoginPage() { 255 | // 로그인 페이지 표시 256 | } // good 257 | ``` 258 | 259 | 조건부 표현식 사용 260 | 261 | ```java 262 | // if 문 사용 263 | if (user.getAge() >= 19) { 264 | // 성인임을 알리는 메시지 출력 265 | } else { 266 | // 미성년임을 알리는 메시지 출력 267 | } // bad 268 | ``` 269 | 270 | ```java 271 | boolean isAdult = user.getAge() >= 19; 272 | 273 | if (isAdult) { 274 | // 성인임을 알리는 메시지 출력 275 | } else { 276 | // 미성년임을 알리는 메시지 출력 277 | } // good 278 | ``` 279 | 280 | ### 1. 그릇된 정보를 피해라. 281 | 282 | 영문자 O와 숫자 0 과 같이 구분이 어려운 조합을 정보로 제공하지 않아야 한다. 283 | 284 | 유사한 개념은 유사한 표기법을 사용해야 하지만(일관성) 다른 개념과 혼용될 수 있는 정보를 제공해서는 안된다. 285 | 286 | ### 2. 의미있게 구분하라. 287 | 288 | 문자열을 카피할 때 파라미터로 (char a1[], char a2[]) 보다는 (char source[], char destination[]) 이 훨씬 읽기 편하다. 289 | 290 | ### 3. 검색하기 쉬운 이름, 발음하기 쉬운 이름을 사용하라. 291 | 292 | ### 4. 클래스 이름 규칙 293 | 294 | 클래스 이름과 객체 이름은 명사나 명사구가 적합하다. 295 | 296 | ex)Customer, Account 등 (info, data, manager 등은 피한다) 297 | 298 | ### 5. 메서드 이름 규칙 299 | 300 | 동사나 동사구가 적합하다. 301 | 302 | ex) registerUser, deletePage 303 | 304 | ### 6. 한 개념에 한 단어를 사용하라. 305 | 306 | ex) fetch, get, retrieve등과 같이 한가지 기능을 하는 여러 표기나 여러 단어는 피하라. 307 | 308 | # 함수 (메서드) 309 | 310 | > - 함수는 작게 만들어야 한다. 311 | > - 한가지만 수행해야 한다. 312 | > - 함수당 하나의 추상화 수준을 유지한다. 313 | > - 위에서 아래로 코드 읽기 : 내려가기 규칙, 위에서 부터 아래로 코드가 이동되어야 한다. 314 | > - 개인적인 의견이지만 한 메서드에 다른 메서드들이 존재한다면 먼저 읽히는 메서드가 바로 밑에 존재하는게 읽기 편하다. 315 | > 316 | 317 | ### 1. switch 문 318 | 319 | switch 문은 기본적인 분기 문법이 길어 작게 만들기 어렵다.
또한 기능상으로 여러개의 조건에 맞는 처리를 하기 때문에 320 | 한가지 기능을 수행하지 못하므로, 이를 저차원 클래스로 숨기고 변경되는 요인에 따른 영향도를 최소화 해야 한다. 321 | 322 | 아래의 예제를 살펴보자. 각 근무형태에 따른 급여를 계산하는 로직이다. 323 | 324 | ```java 325 | 326 | /** 327 | * @author CodeVillains 328 | * @description 직관적으로 switch 조건식을 통해 작성한 급여 계산 로직. 329 | * 장점 : switch 구문을 이용해서 로직을 직관적으로 이해할 수 있다. 단순한 로직의 경우 직접 구현하는 형태로 초보자용 코드로 적합하다. 330 | * 단점 : 코드 중복 - switch 문 내의 각 사례가 서로 다른 계산을 처리하여 중복이 발생하므로 일정 수준의 코드 중복이 발생한다. 331 | * OCP 위반 - 직원 유형이 추가된다거나 급여 계산식이 바뀔 경우 스위치 구문내부가 급격하게 늘어나고 각각의 코드 로직이 복잡해져 유지관리가 어렵다.(개방폐쇄 원칙 위반) 332 | */ 333 | public class EmployeePaySwitch { 334 | 335 | public static void main(String[] args) { 336 | String employeeType = "Contract"; // 직원 유형 설정 337 | double hoursWorked = 40; // 근무 시간 338 | double hourlyRate = 15000; // 시간당 급여 339 | 340 | double pay = calculatePay(employeeType, hoursWorked, hourlyRate); 341 | System.out.println("직원 유형: " + employeeType); 342 | System.out.println("근무 시간: " + hoursWorked + "시간"); 343 | System.out.println("시간당 급여: " + hourlyRate + "원"); 344 | System.out.println("총 급여: " + pay + "원"); 345 | } 346 | 347 | public static double calculatePay(String employeeType, double hoursWorked, double hourlyRate) { 348 | double pay = 0; 349 | 350 | switch (employeeType) { 351 | case "Regular": 352 | pay = hoursWorked * hourlyRate + 50000; // 정규직 추가 급여 353 | break; 354 | case "Contract": 355 | pay = hoursWorked * hourlyRate; 356 | break; 357 | case "Temporary": 358 | pay = hoursWorked * hourlyRate * 0.8; // 임시직 급여율 359 | break; 360 | case "Asist": 361 | pay = hoursWorked * hourlyRate * 0.7; // 어시스트 급여율 362 | break; 363 | default: 364 | System.out.println("유효하지 않은 직원 유형입니다."); 365 | } 366 | return pay; 367 | } 368 | } 369 | ``` 370 | 371 | 대부분의 경우 이 코드에서 좀 더 개선해보자면 아래와 같은 형태로 작성할 것이다. 372 | 373 | ``` java 374 | public class EmployeePaySwitchRefactor { 375 | 376 | public static void main(String[] args) { 377 | String employeeType = "Contract"; // 직원 유형 설정 378 | double hoursWorked = 40; // 근무 시간 379 | double hourlyRate = 15000; // 시간당 급여 380 | 381 | double pay = calculatePay(employeeType, hoursWorked, hourlyRate); 382 | System.out.println("직원 유형: " + employeeType); 383 | System.out.println("근무 시간: " + hoursWorked + "시간"); 384 | System.out.println("시간당 급여: " + hourlyRate + "원"); 385 | System.out.println("총 급여: " + pay + "원"); 386 | } 387 | 388 | public static double calculatePay(String employeeType, double hoursWorked, double hourlyRate) { 389 | double pay = 0; 390 | 391 | switch (employeeType) { 392 | case "Regular": 393 | pay = calculatePayWithAdjustment(hoursWorked, hourlyRate, 1.0); // 정규직 추가 급여 394 | break; 395 | case "Contract": 396 | pay = calculatePayWithAdjustment(hoursWorked, hourlyRate, 1.0); 397 | break; 398 | case "Temporary": 399 | pay = calculatePayWithAdjustment(hoursWorked, hourlyRate, 0.8); // 임시직 급여율 400 | break; 401 | case "Assist": 402 | pay = calculatePayWithAdjustment(hoursWorked, hourlyRate, 0.7); // 어시스트 급여율 403 | break; 404 | default: 405 | System.out.println("유효하지 않은 직원 유형입니다."); 406 | } 407 | 408 | return pay; 409 | } 410 | 411 | private static double calculatePayWithAdjustment(double hoursWorked, double hourlyRate, double adjustment) { 412 | return hoursWorked * hourlyRate * adjustment; 413 | } 414 | } 415 | ``` 416 | 417 | switch 구문 내에서 로직이 들어가는 부분을 공통 메서드로 분리하였다. 418 | 419 | 장점으로는 공통 급여 계산 로직을 별도의 메서드로 추출하면 중복이 줄어들고 코드 재사용성이 좋아졌다는 것과 420 | 각 계산 로직이 메서드 호출 형태로 변경 되어 가독성 및 변경 영향도가 줄어들어 유지보수가 더 쉽다는 것을 들 수 있다. 421 |
그러나 아직 switch 문에 로직이 존재하여 근무 형태애 따른 정책이 변경되면 내부 로직에 수정이 필요할 수 있고(OCP) switch 문이 주요 흐름이므로 메서드가 추가 된다거나 하는 등의 확장성에는 완전히 유용해보이지 않는다. 422 | 423 | 따라서 이러한 switch 구문을 어딘가로 위임하거나 상속 관계로 숨겨 실제 로직을 파생 클래스의 메서드를 호출하는 방식으로 변경하는게 더 OOP에 적합하다. 424 | 425 | 여기서 공통 기능을 수행하는 급여 계산은 구현하는 모든 근무형태 클래스에서 각각의 로직을 가져야 하므로 인터페이스로 정의한다. 426 | 427 | ```java 428 | /** 429 | * 급여 계산에 대한 공통 인터페이스 정의 430 | */ 431 | public interface PayCalculator { 432 | double calculatePay(double hoursWorked, double hourlyRate); 433 | } 434 | ``` 435 | 436 | 정규직인 경우 급여 계산식에서 + 50000이 추가되므로 아래와 같은 계산식이 된다. 437 | ```java 438 | public class RegularPayCalculator implements PayCalculator { 439 | 440 | @Override 441 | public double calculatePay(double hoursWorked, double hourlyRate) { 442 | return hoursWorked * hourlyRate + 50000; 443 | } 444 | } 445 | ``` 446 | 447 | 계약직과 임시직, 어시스트 계산식도 각각 구현해준다. 448 | ```java 449 | // 계약직 450 | public class ContractPayCalculator implements PayCalculator { 451 | @Override 452 | public double calculatePay(double hoursWorked, double hourlyRate) { 453 | return hoursWorked * hourlyRate; 454 | } 455 | } 456 | 457 | ``` 458 | 459 | ```java 460 | // 임시직 461 | public class TemporaryPayCalculator implements PayCalculator { 462 | @Override 463 | public double calculatePay(double hoursWorked, double hourlyRate) { 464 | return hoursWorked * hourlyRate * 0.8; // Temporary employe 465 | } 466 | } 467 | ``` 468 | 469 | 470 | ```java 471 | // 어시스트 472 | class AssistPayCalculator implements PayCalculator { 473 | @Override 474 | public double calculatePay(double hoursWorked, double hourlyRate) { 475 | return hoursWorked * hourlyRate * 0.7; // Assist employee 476 | } 477 | } 478 | ``` 479 | 480 | 여기까지가 구체적인 급여 계산 클래스들이다. 이제 이를 호출하는 클라이언트(메인 코드)와 인스턴스를 생성하는 코드를 살펴보자. 481 | 482 | ```java 483 | public class PayCalculatorFactory { 484 | 485 | static PayCalculator createPayCalculator(String employeeType) { 486 | switch (employeeType) { 487 | case "Regular": 488 | return new RegularPayCalculator(); 489 | case "Contract": 490 | return new ContractPayCalculator(); 491 | case "Temporary": 492 | return new TemporaryPayCalculator(); 493 | case "Assist": 494 | return new AssistPayCalculator(); 495 | default: 496 | return null; 497 | } 498 | } 499 | } 500 | 501 | ``` 502 | createPayCalculator 메서드를 통해 적절한 급여 계산 인스턴스를 생성해온다. 그 결과에 따라 switch 구문에 따른 직원 유형의 계산 결과를 반환한다. 503 | 504 | 505 | ```java 506 | public class EmployeePayAbstractFatoryMain { 507 | 508 | public static void main(String[] args) { 509 | String employeeType = "Contract"; // Employee type setting 510 | double hoursWorked = 40; // Hours worked 511 | double hourlyRate = 15000; // Hourly rate 512 | 513 | PayCalculator payCalculator = PayCalculatorFactory.createPayCalculator(employeeType); 514 | if (payCalculator != null) { 515 | double pay = payCalculator.calculatePay(hoursWorked, hourlyRate); 516 | System.out.println("Employee type: " + employeeType); 517 | System.out.println("Hours worked: " + hoursWorked + " hours"); 518 | System.out.println("Hourly rate: " + hourlyRate + " won"); 519 | System.out.println("Total pay: " + pay + " won"); 520 | } else { 521 | System.out.println("Invalid employee type."); 522 | } 523 | } 524 | } 525 | ``` 526 | 실제 호출 부분은 switch 구문이 보이지 않고 팩토리를 통해 생성한 직원 유형에 따른 계산식을 호출 한 뒤 결과값만을 출력(return) 해주는 역할이 끝이다. 527 | 528 | 529 | 이 코드는 각 직원 유형에 대한 급여 계산을 모듈화하고, 추가적인 직원 유형이 추가되거나 변경되더라도 수정을 최소화한다. 530 | 물론 새로운 직원 유형을 추가해야 하는 경우 여전히 스위치 문 내에서 새로운 케이스를 처리해주어야 하기 때문에 OCP를 완전히 구현한 것은 아니다. 531 | 532 | 그러나 원본 코드와 비교한다면 더 유지보수 하기 쉽고 기능별 모듈화를 통해 객체 지향적인 코드의 방향에 맞게 설계가 가능하다는 것을 알수 있다. 533 | 534 | 만약 해당 코드에 switch 구문을 제거하고 전략 패턴으로 좀 더 완벽하게 분리하는 것을 추가 하고 싶다면 아래의 예제 코드들을 살펴보면 도움이 될 것이다. 535 | 536 | > 537 | > https://github.com/villainscode/coding-guide/tree/main/src/main/java/net/mingleup/guide/employee/strategy 538 | > 539 | 비슷한 구조로 리팩토링 되어있으므로 이해하기 어렵지 않을 것이다. 540 | 541 | ### 2. 서술적인 이름을 사용하라. 542 | 메서드가 하는 일을 좀 더 잘 표현할 수 있도록 서술형 이름을 지어야 한다. 543 | 544 | 모듈 내에서 함수 이름은 일관성(같은 문구나 명사, 동사등의 사용 규칙)을 유지해야 한다. 545 | 546 | ### 3. 메서드 인수 547 | 이상적인 인수의 갯수는 '없으면 없을수록 좋다' 이다. 548 | 인수를 주더라도 3개 이상은 지양하고, 4개 부터는 특별한 경우를 제외하고는 객체를 넘겨서 처리하는 것이 낫다. 549 | 또한 인수값으로 flag를 넘기다는 것은 메서드에서 여러개의 처리를 하겠다는 의미이므로 boolean 을 통해 내부에서 값을 조작하는 행위는 지양해야 한다. 550 | 551 | ### 4. 명령과 조회를 분리해라. 552 | 553 | ### 5. DRY (Don't Repaet Yourself) 554 | 555 | ### 6. 주석은 코드로 대체할 수 있어야 한다. 556 | 즉, 주석은 나쁜 코드를 보완하지 못한다. 557 | > 558 | > [Code as Docuementation](https://martinfowler.com/bliki/CodeAsDocumentation.html) by Martin fowler 559 | > 560 | 단순 정보만 주는 주석은 아무런 가치가 없고 오히려 코드의 의도만 해치게 되고 잘못된 정보를 제공할 가능성이 높으므로 필수적인 내용만을 기록하는게 좋다. 561 | 562 | # 형식 맞추기 563 | 들여쓰기, 빈 행으로 가시성을 확보하거나 적절한 수준의 행 길이를 맞추는 등의 노력은 결국 코드의 품질과 가독성에 직결된다. 564 | 20자~60바 정도의 가로행이 적절하고 그보다 더 짧을 수록 인지율이 올라간다.
565 | 또한 팀이라면 팀 내부에서 명명법이나 코딩 규칙을 마련해서 이 형식을 따라 개발을 진행해야 한다. 566 | 567 | # 객체와 자료 구조 568 | ### 디미터의 법칙 : 오직 하나의 점(.)에 의해 호출되는 메서드만 호출해야 한다. 569 | 570 | ```java 571 | public class Car { 572 | private Engine engine; 573 | 574 | public Car(Engine engine) { 575 | this.engine = engine; 576 | } 577 | 578 | public Engine getEngine() { 579 | return engine; 580 | } 581 | } 582 | 583 | public class Engine { 584 | public void start() { 585 | // 엔진을 시작하는 로직 586 | } 587 | } 588 | 589 | public class Driver { 590 | public void drive(Car car) { 591 | // 디미터의 법칙 위반: Car 객체에서 엔진에 직접 접근하여 메서드를 호출함 592 | car.getEngine().start(); // 엔진을 시작하는 메서드 호출 593 | } 594 | } 595 | ``` 596 | Driver 클래스의 drive 메서드는 Car 클래스의 getEngine을 통해 start 메서드를 호출한다. 이렇게 하는 대신에 Car 클래스 내에서 엔진을 시작하는 메서드를 호출하도록 변경해야 한다. 597 | 598 | ```java 599 | public class Car { 600 | private Engine engine; 601 | 602 | public Car(Engine engine) { 603 | this.engine = engine; 604 | } 605 | 606 | public void startEngine() { 607 | engine.start(); 608 | } 609 | } 610 | 611 | public class Engine { 612 | public void start() { 613 | // 엔진을 시작하는 로직 614 | } 615 | } 616 | 617 | public class Driver { 618 | public void drive(Car car) { 619 | car.startEngine(); // 디미터의 법칙 준수: Car 클래스에서 엔진 시작 메서드를 호출함 620 | } 621 | } 622 | ``` 623 | Driver 클래스에서는 Car 객체의 startEngine 메서드만 호출하여 엔진을 시작할 수 있으며, 이로써 Car 클래스가 엔진에 대한 내부 구현을 캡슐화하고 외부로 노출하지 않게 된다. 624 | 625 | ### 자료 전달 객체 626 | DTO(Data Transfer Object)를 통해 전송해야 하는 데이터를 명확하게 정의하고 모듈간의 결합도를 낮춰야 한다.
627 | 데이터를 전달하는 과정에서 객체를 직접 사용하면 객체의 내부 구조에 의존하게 되어 결합도가 높아지고, 유연성과 재사용성이 저하된다.
628 | 또한 다른 서비스를 호출하여 데이터를 전송할 때 불필요하게 많은 데이터가 전송될 가능성이 있으므로 적절한 전달 객체를 통해 데이터 전송의 명확성과 안전성을 확보하고, 시스템의 결합도를 낮추어 유지 보수성과 확장성을 향상시켜야 한다. 629 | 630 | # 오류 처리 631 | 632 | 633 | ### 오류 코드보다 예외를 사용하라. 634 | 오류 코드 대신에 예외를 사용하는 것을 권장한다. 예외를 사용하면 코드의 가독성이 향상되고 오류 처리가 명확해지며, 코드의 유연성과 안정성이 향상된다. 635 | 636 | ````java 637 | public class Calculator { 638 | public int divide(int dividend, int divisor) { 639 | if (divisor == 0) { 640 | return -1; // 오류 코드 반환 641 | } 642 | return dividend / divisor; 643 | } 644 | } 645 | ```` 646 | 이런 코드는 예외가 발생한건지 로직에서 더 이상 수행할 수 없는 것인지 판단하기 어렵다. 647 | 아래와 같이 바꿔야 한다. 648 | 649 | ```java 650 | public class Calculator { 651 | public int divide(int dividend, int divisor) { 652 | if (divisor == 0) { 653 | throw new IllegalArgumentException("Divisor cannot be zero"); // 예외 발생 654 | } 655 | return dividend / divisor; 656 | } 657 | } 658 | ``` 659 | 660 | ### null을 반환하지 마라. 661 | null을 반환하는 것은 호출자가 해당 값이 null인지 확인하고 처리해야 하므로 코드의 가독성과 안전성이 저하될 수 있다. 662 | 따라서 return null 과 같은 코드를 넘겨주지 말고 명시적으로 예외를 발생시켜야 한다. 663 | ```java 664 | if (user == null) { 665 | throw new IllegalArgumentException("User not found with username: " + username); 666 | } 667 | ``` 668 | 669 | # 클래스 670 | 671 | ### 클래스 작성 규칙 672 | 클래스 체계상 클래스를 정의하는 표준 자바 규칙은 아래와 같다. 673 | 1. 상수(Constant): 클래스 수준에서 사용되는 상수를 정의. 정적 공개 상수가 맨 처음 나오고 다음으로 정적 비공개가 나온다. 674 | 2. 멤버 변수(Fields): 클래스의 상태를 표현하는 멤버 변수를 정의. 675 | 3. 생성자(Constructor): 클래스의 객체를 생성할 때 호출되는 생성자를 정의. 676 | 4. 메서드(Methods): public 메서드가 나오고 그 뒤에 private 호출 메서드를 순차적으로 작성해준다. 677 | 678 | 아래의 예제를 보자. 679 | ```java 680 | public class ExampleClass { 681 | 682 | // 상수 683 | private static final int MAX_SIZE = 100; 684 | private static final String DEFAULT_NAME = "Default"; 685 | 686 | // 멤버 변수 687 | private int size; 688 | private String name; 689 | 690 | // 생성자 691 | public ExampleClass() { 692 | this.size = 0; 693 | this.name = DEFAULT_NAME; 694 | } 695 | 696 | public ExampleClass(int size, String name) { 697 | this.size = size; 698 | this.name = name; 699 | } 700 | 701 | // 메서드 702 | public int getSize() { 703 | return size; 704 | } 705 | 706 | public void setSize(int size) { 707 | this.size = size; 708 | } 709 | 710 | public String getName() { 711 | return name; 712 | } 713 | 714 | public void setName(String name) { 715 | this.name = name; 716 | } 717 | 718 | // 중첩 클래스 719 | private static class NestedClass { 720 | // 중첩 클래스의 멤버 변수, 생성자, 메서드 등 721 | } 722 | } 723 | ``` 724 | 725 | ### 클래스는 작게 설계 해야한다. 726 | ### SRP 727 | 728 | SRP(Single Responsibility Principle, 단일 책임 원칙)는 소프트웨어 개발에서 클래스나 모듈은 하나의 책임만 가져야 한다는 의미로, 이는 클래스가 변경되어야 하는 이유가 단 하나뿐이어야 함을 의미한다. 729 | ```java 730 | public class ReportGenerator { 731 | 732 | public void generateReport() { 733 | // 보고서 생성 로직 734 | String data = fetchDataFromDatabase(); 735 | formatData(data); 736 | saveReportToFile(data); 737 | } 738 | 739 | private String fetchDataFromDatabase() { 740 | // 데이터베이스에서 데이터를 가져오는 로직 741 | return "Data from database"; 742 | } 743 | 744 | private void formatData(String data) { 745 | // 데이터 포맷팅 로직 746 | System.out.println("Formatting data: " + data); 747 | } 748 | 749 | private void saveReportToFile(String data) { 750 | // 파일에 보고서를 저장하는 로직 751 | System.out.println("Saving report to file: " + data); 752 | } 753 | } 754 | ``` 755 | 이 코드는 보고서를 생성할 때 각종 타입에 따라 여러 역할을 수행하도록 구현되어 있다. 756 | 이는 SRP 위반이고 클래스가 변경 되어야 하는 이유가 다양하기 때문에 유지보수가 어려워진다. 따라서 아래의 방식으로 변경해야 한다. 757 | 758 | ```java 759 | public class ReportGenerator { 760 | 761 | private DatabaseFetcher databaseFetcher; 762 | private DataFormatter dataFormatter; 763 | private FileSaver fileSaver; 764 | 765 | public ReportGenerator(DatabaseFetcher databaseFetcher, DataFormatter dataFormatter, FileSaver fileSaver) { 766 | this.databaseFetcher = databaseFetcher; 767 | this.dataFormatter = dataFormatter; 768 | this.fileSaver = fileSaver; 769 | } 770 | 771 | public void generateReport() { 772 | String data = databaseFetcher.fetchData(); 773 | String formattedData = dataFormatter.formatData(data); 774 | fileSaver.saveToFile(formattedData); 775 | } 776 | } 777 | ``` 778 | 779 | ```java 780 | public class DatabaseFetcher { 781 | 782 | public String fetchData() { 783 | // 데이터베이스에서 데이터를 가져오는 로직 784 | return "Data from database"; 785 | } 786 | } 787 | ``` 788 | ```java 789 | public class DataFormatter { 790 | 791 | public String formatData(String data) { 792 | // 데이터 포맷팅 로직 793 | return "Formatted data: " + data; 794 | } 795 | } 796 | ``` 797 | 798 | 799 | ```java 800 | public class FileSaver { 801 | 802 | public void saveToFile(String data) { 803 | // 파일에 보고서를 저장하는 로직 804 | System.out.println("Saving report to file: " + data); 805 | } 806 | } 807 | ``` 808 | ReportGenerator 클래스가 보고서 생성만 담당하도록 변경되었고 데이터 가져오기(DatabaseFetcher), 데이터 포맷팅(DataFormatter), 파일 저장(FileSaver) 등의 역할은 각각의 클래스로 분리되었다. 809 |
810 | 이렇게 하면 각 클래스는 단일 책임을 갖게 되고, 변경이 발생할 경우 해당 클래스만 수정하면 되므로 유지 보수가 용이해진다. 811 | 812 | ### 응집도(Cohesion) 813 | 응집도가 높은 코드는 가급적 작게 나눠서 응집도를 낮춰야 한다. 단, 한가지 기능만을 수행하기 위한 목적이라면 상관없다. 814 | 815 | 816 | 817 | # 코드 기본 사용 규칙 모음 818 | **** 819 | 용례를 살펴보기 전에 알아둬야 할 것 820 | > 821 | > 822 | > 하나의 코드 블럭으로 표현한 부분은 상단 설명에 대한 코드 샘플이다. 823 | > 824 | > 상단 설명은 **Bold**로 표기 하였다. 825 | > 826 | > 코드를 비교해야 하는 부분은 위와 아래로 나눠 위는 BadCase, 아래는 GoodCase로 기술하였다. 827 | > 828 | 829 | ### 규칙 1. 파일은 UTF-8로 인코딩 한다. 830 | 831 | import 구문은 줄 바꿈 하지 않는다. (한 줄에 전체를 기술한다.) 832 | 833 | ### 규칙 2. 패키지 이름은 소문자이고, 언더라인을 쓰지 않는다. 834 | 835 | 패키지 구조는 최상위 도메인부터 역순으로 표기하는것이 컨벤션이다. 836 | 837 | ```java 838 | com.company.project 839 | ``` 840 | 841 | ### 규칙 3. 클래스 이름은 UpperCamelCase (PascalCase) 로 작성한다. 단어와 단어 사이에는 대문자로 표기하고 명사로 명명한다. 842 | 843 | 약어를 사용하지 않는다. 인터페이스도 동일하다. 844 | 845 | ```bash 846 | CustomerController # 클래스 847 | CustomerService # 클래스 848 | Runnable # 인터페이스 849 | ``` 850 | 851 | ### 규칙 4 .메소드 이름은 lowerCamelCase 로 작성한다. 메소드명은 동사 또는 동사구이다. 852 | 853 | 네이밍은 동사나 동사구로 짓고, boolean 값을 반환하는 경우는 is나 has로 시작하고 형용사로 기능하는 단어로 끝난다. 854 | 855 | 객체의 타입을 바꿔서, 다른 타입의 또 다른 객체를 반환하는 인스턴스 메서드의 이름은 보통 toType 형태로 짓는다.(toString, toArray) 856 | 857 | ``` 858 | getName() 859 | isEmpty() 860 | hasLevel() 861 | toString() 862 | ``` 863 | 864 | ### 규칙 5. 너무 짧은 변수명, 메소드명은 지양하고 의미있는 이름으로 지어야 한다. 메소드 명이나 변수명은 의미를 전달하는 용도로 작성해야 한다. 865 | 866 | ### 규칙 6. 상수는 CONST_CASE 이다. (UPPER_SNAKE_CASE) 867 | 868 | - 상수는 내용이 변경 불가능 해야 한다. 869 | - static final 필드이어야 한다. 870 | 871 | 875 | 876 | ### 규칙 7. 상수가 아닌 필드는 lowerCamelCase로 작성한다. 877 | 878 | 매개변수의 이름은 lowerCamelCase로 작성한다. 매개변수가 4개를 초과할 경우 Object 타입으로 정의하는게 OCP(**Open-Closed Principle)** 설계에 맞는 동작 방식이다. 879 | 880 | 지역변수 이름은 lowerCamelCase로 작성한다. 변수 이름은 짧지만 의미가 있어야 하고, 언더스코어나 특수문자를 쓰면 안된다. 881 | 882 | > *상수는 static final 키워드로 정의된 필드이다. 이 필드의 내용은 불변하며, 메소드는 부작용이 있으면 안된다. 이는 원시 자료형, String, 불변 타입, 불변 타입의 불변 collection을 포함한다. 만약 어떤 인스턴스의 상태가 바뀔 수 있다면 이는 상수가 아니다.* 883 | > 884 | 885 | ```java 886 | // 상수 887 | static final int NUMBER = 5; 888 | static final ImmutableList NAMES = ImmutableList.of("Ed", "Ann"); 889 | static final ImmutableMap AGES = ImmutableMap.of("Ed", 35, "Ann", 32); 890 | static final Joiner COMMA_JOINER = Joiner.on(','); // Joiner는 불변이기 때문에. 891 | static final SomeMutableType[] EMPTY_ARRAY = {}; 892 | enum SomeEnum { ENUM_CONSTANT } 893 | 894 | // 상수 아님 895 | static String nonFinal = "non-final"; // final 없음 896 | final String nonStatic = "non-static"; // static 아님 897 | static final Set mutableCollection = new HashSet(); // 가변타입 String 사용 898 | static final ImmutableSet mutableElements = ImmutableSet.of(mutable); // 가변타입의 변수로 초기화 함 899 | static final ImmutableMap mutableValues = 900 | ImmutableMap.of("Ed", mutableInstance, "Ann", mutableInstance2); // 가변타입의 변수로 초기화 함 901 | static final Logger logger = Logger.getLogger(MyClass.getName()); 902 | static final String[] nonEmptyArray = {"these", "can", "change"}; 903 | 904 | ``` 905 | 906 | ### 규칙 8. 변수는 lowerCamelCase로 작성한다. 907 | 908 | 단, 변수에서 boolean 타입을 정의 할 경우 메소드와는 다르게 isXXX 와 같은 이름은 피하도록 한다. 일부 직렬화 예외가 발생할 수 있다. 909 | 910 | 임시 변수 (반복문을 순회하는 경우) 라도 가급적 한글자의 변수명 보다는 의미가 있는 이름으로 짓는것이 낫다. 911 | 912 | # Usage (코드 예제 모음) 913 | 914 | --- 915 | 916 | ### 1. 괄호는 Egyptian brackets 을 따른다. 917 | 918 | ```java 919 | return () -> { 920 | while (condition()) { 921 | method(); 922 | } 923 | }; 924 | 925 | return new MyClass() { 926 | @Override public void method() { 927 | if (condition()) { 928 | try { 929 | something(); 930 | } catch (ProblemException e) { 931 | recover(); 932 | } 933 | } else if (otherCondition()) { 934 | somethingElse(); 935 | } else { 936 | lastThing(); 937 | } 938 | } 939 | }; 940 | ``` 941 | 942 | ### 2. 빈 블럭이나 block-like construct 는 아래와 같이 사용 가능하지만 멀티 블럭에 섞어서 사용할순 없다. 943 | 944 | ```java 945 | // 가능 946 | void doNothing() {} 947 | 948 | // 가능 949 | void doNothingElse() { 950 | } 951 | // 불가능 : multi-block 에서 괄호의 줄바꿈과 빈 블럭을 동시에 사용하지 말것 952 | try { 953 | doSomething(); 954 | } catch (Exception e) {} 955 | ``` 956 | 957 | ### 3. 공백과 빈줄 958 | 959 | - 들여쓰기 (Indentation)은 4칸의 공백이어야 한다. (4 spaces key) 960 | - “if”, “while”, “return”, “catch”, “switch”, “for” 등의 키워드와 이어지는 괄호에는 공백이 있어야 한다. 961 | - 세미콜론, 쉼표 뒤에는 공백이 있어야 한다. 962 | - 코드의 주요 부분을 식별하기 위해 빈 줄을 넣어준다. 예를 들어, 변수 선언 부분과 변수를 이용한 로직이 들어간다면 이 부분은 구분을 해주기 위해 빈 줄을 삽입해주는것이 좋다. 963 | 964 | ### 4. Static Factory Method 의 관행적인 명명 965 | 966 | - from: 하나의 매개변수를 받아서 해당 타입의 인스턴스를 생성 (형변환) 967 | - valueOf : 매개변수와 동일한 값을 갖는 인스턴스를 반환 968 | - of: 여러개의 매개변수를 받아서 인스턴스를 생성 (aggregate) 969 | - instance or getInstance: (매개변수를 받는다면 매개변수로 명시한 ) 인스턴스를 반환하지만 동일한 인스턴스임을 보장하지 않는다. 970 | - 보통 singleton을 구현할때 많이 사용하는 네이밍이지만 싱글톤의 경우 getInstance는 매개변수를 사용하지 않고 유일한 인스턴스를 반환한다. 971 | - create or newInstance: getInstance 와 유사하지만 매번 새로운 인스턴스를 반환한다. 972 | - getType: getInstance와 같으나 호출하는 클래스가 아닌 다른 타입의 인스턴스를 반환할때 사용 973 | - newType — newInstance와 비슷하지만 호출하는 클래스가 아닌 다른 타입의 인스턴슬 반환할 때 사용. Type은 팩토리 메서드에서 반환된 객체의 유형을 나타낸다. 974 | 975 | ### 5. List 타입에서 빈 컬렉션을 반환할때는 null이나 new 생성 리스트 타입 보다는 Empty Collection을 사용하는것이 GC에 유리하다. 976 | 977 | ```java 978 | List list = new ArrayList(); 979 | Set set = new HashSet(); 980 | Map map = new HashMap(); 981 | ``` 982 | 983 | ```java 984 | List list = Collections.emptyList(); 985 | Set set = Collections.emptySet(); 986 | Map map = Collections.emptyMap() 987 | ``` 988 | 989 | ### 6. foreach 루프에서 콜렉션의 요소를 추가하거나 삭제하지 말것 990 | 991 | - Iterator 를 통해 요소를 조작하도록 한다. 992 | 993 | ```java 994 | List a = new ArrayList(); 995 | a.add("1"); 996 | a.add("2"); 997 | for (String temp : a) { 998 | if ("2".equals(temp)){ 999 | a.remove(temp); 1000 | } 1001 | } // bad 1002 | ``` 1003 | 1004 | ```java 1005 | Iterator it = a.iterator(); 1006 | while (it.hasNext()) { 1007 | String temp = it.next(); 1008 | if (delete condition) { 1009 | it.remove(); 1010 | } 1011 | } // good 1012 | ``` 1013 | 1014 | ### 7. Key, Value 콜렉션에서 null이 저장되는지 체크해두어야 한다. 1015 | 1016 | | Collection | Key | Value | Super | Note | 1017 | | --- | --- | --- | --- | --- | 1018 | | Hashtable | Null is not allowed | Null is not allowed | Dictionary | Thread-safe | 1019 | | ConcurrentHashMap | Null is not allowed | Null is not allowed | AbstractMap | Segment lock | 1020 | | TreeMap | Null is not allowed | Null is allowed | AbstractMap | Thread-unsafe | 1021 | | HashMap | Null is allowed | Null is allowed | AbstractMap | Thread-unsafe | 1022 | 1023 | ### 8. Thread 1024 | 1025 | 의미 있는 스레드 이름은 오류 정보를 추적하는 데 도움이 되므로 스레드 또는 스레드 풀을 생성할 때 이름을 지정하기를 추천한다. 1026 | 1027 | ```java 1028 | public class TimerTaskThread extends Thread { 1029 | public TimerTaskThread() { 1030 | super.setName("TimerTaskThread"); 1031 | ... 1032 | } 1033 | } 1034 | ``` 1035 | 1036 | 스레드는 스레드 풀에서 제공되어야 한다. 명시적으로 스레드를 생성하는것은 허용되지 않는다. 1037 | 1038 | - 스레드 풀을 사용하면 스레드 생성 및 소멸 시간을 줄이고 시스템 리소스를 절약할 수 있다. 1039 | - 스레드 풀을 사용하지 않으면 유사한 스레드가 많이 생성되어 "메모리 부족" 또는 오버 스위칭 문제가 발생한다. 1040 | 1041 | ### 9. String 타입에 적절하지 않은 데이터를 표기하지 말아야 한다. 1042 | 1043 | ```java 1044 | String value = “True”; // bad 1045 | ``` 1046 | 1047 | ```java 1048 | boolean value = true; // good 1049 | ``` 1050 | 1051 | ### 10. String의 초기화에는 StringUtils.EMPTY을 사용하는 것을 권장한다. 1052 | 1053 | ```java 1054 | String value = “”; // bad 1055 | ``` 1056 | 1057 | ```java 1058 | String value = StringUtils.EMPTY; // good 1059 | ``` 1060 | 1061 | ### 11. 불필요한 연산은 사용하지 말자. 1062 | 1063 | ```java 1064 | public class BadExample { 1065 | public void foo() { 1066 | boolean flag = false; 1067 | if (flag == true) { // bad 1068 | // some code 1069 | } else { 1070 | // some code 1071 | } 1072 | } 1073 | } 1074 | ``` 1075 | 1076 | ```java 1077 | public class GoodExample { 1078 | public void foo() { 1079 | boolean flag = false; 1080 | if (flag) { // good 1081 | // some code 1082 | } else { 1083 | // some code 1084 | } 1085 | } 1086 | } 1087 | ``` 1088 | 1089 | 1090 | ### 12. Optional 사용 1091 | 1092 | Exception 혹은 null을 반환하는 method에는 Optional을 사용한다. 1093 | 1094 | - Optional 값은 postfix로 Optional을 사용한다.
1095 | - Exception은 정말 Exception이 필요한 부분에서 사용한다. 정상적인 흐름을 통제하기 위해서는 Exception 보다는 Optional을 사용한다. 1096 | - Optional은 null을 반환될 가능성이 있다는 것을 명시적으로 제시한다. 1097 | - Collection을 반환하는 경우에는 Optional을 사용하지 않는다. 1098 | 1099 | ```java 1100 | public ExportService getExportRequest() { 1101 | ... 1102 | ExportService exportInfo = getExportRequest(); 1103 | if(exportInfo == null){ // bad 1104 | ... 1105 | } 1106 | } 1107 | ``` 1108 | ```java 1109 | public Optional getExportRequest(){ 1110 | ... 1111 | Optional exportInfoOptional = getExportRequest(); 1112 | if(exportInfoOptional.isEmpty()){ 1113 | // handling the null 1114 | } 1115 | ExportService exportInfo = exportInfoOptional.get(); 1116 | } 1117 | ``` 1118 | 1119 | ### 13. Predicate과 Valdator 구분해서 사용 1120 | Predicate 와 Validator 의 용도가 다르다. 1121 | 1122 | Predicate 1123 | - https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html 1124 | - Represents a predicate (boolean-valued function) of one argument. 1125 | 1126 | 1127 | Validator 1128 | - https://en.wikiversity.org/wiki/Object-Oriented_Programming/Validation 1129 | - Validate data Generate and handle exceptions Use assertions to validate parameter assumptions 1130 | 1131 | 1132 | 그러므로 정리하면 1133 | 1134 | Predicate는 1개의 arg 을 받아서 true/false 리턴하는 용도으로 사용한다. 1135 | 1136 | Validator는 데이타 유효성을 판단하는 용도으로 경우에 따라서 exception 발생시킨다. 1137 | 1138 | ### 14. 변수가 너무 많은 클래스 구조는 별도의 객체로 분리하자. 1139 | 1140 | ```java 1141 | public class BadExample { //bad 1142 | private int x; 1143 | private int y; 1144 | private int z; 1145 | private int w; 1146 | 1147 | public void foo() { 1148 | // some code 1149 | } 1150 | } 1151 | ``` 1152 | 1153 | 1154 | ```java 1155 | public class GoodExample { // good 1156 | private Point position; 1157 | 1158 | public void move(int x, int y) { 1159 | // some code 1160 | } 1161 | } 1162 | ``` 1163 | ### 15. 가급적 예외를 구체화 한다. 1164 | 1165 | ```java 1166 | public class BadExample { 1167 | public void foo() { 1168 | try { 1169 | // some code 1170 | } catch (Exception e) { 1171 | // some code 1172 | } 1173 | } 1174 | } 1175 | ``` 1176 | 1177 | ```java 1178 | public class GoodExample { 1179 | public void foo() throws SpecificException { 1180 | // some code 1181 | } 1182 | } 1183 | ``` 1184 | 1185 | ### 16. 짧은 try ~ catch 블럭 권장 1186 | 1187 | 가독성면에서 try catch 블록은 짧아야 하며, 가급적 `exception` 블럭만 처리하여 가독성을 확보해야 한다. 1188 | 1189 | ```java 1190 | void tryCatchBlock() { // bad 1191 | try { 1192 | statement1; 1193 | statement2; 1194 | statement3; // can throw 1195 | statement4; 1196 | } catch (...) { 1197 | …. 1198 | } 1199 | } 1200 | ``` 1201 | 1202 | ```java 1203 | void tryCatchBlock() { // good 1204 | statement1; 1205 | statement2; 1206 | boolean success = false; 1207 | try { 1208 | statement3; // can throw 1209 | success = true; 1210 | } catch (...) { 1211 | … 1212 | } 1213 | 1214 | if (success) { 1215 | statement4; 1216 | } 1217 | } 1218 | ``` 1219 | 1220 | 이에 대한 설명으로는 아래 링크를 참고한다. 1221 | 1222 | tight try catch block (https://stackoverflow.com/questions/2633834/should-java-try-blocks-be-scoped-as-tightly-as-possible) 1223 | ```text 1224 | the size of a try block makes no difference in performance. Performance is affected by actually raising exceptions at runtime, and that's independent of the size of the try block. 1225 | 1226 | However, keeping try blocks small can lead to better programs. 1227 | 1228 | You might catch exceptions to recover and proceed, or you might catch them simply to report them to the caller (or to a human, via some UI). 1229 | 1230 | In the first case, failures from which you can recover are often very specific, and this leads to smaller try blocks. 1231 | 1232 | In the second case, where an exception is caught so that it can be wrapped by another exception and re-thrown, or displayed to the user, small try blocks mean that you know more precisely which operation failed, and the higher-level context in which that call was made. This allows you to create more specific error reports. 1233 | ``` 1234 | 1235 | 1236 | 1237 | 1238 | ### 17. SpringFramework 에서 제공하는 자체 Util 들은 사용하면 안된다. 1239 | 1240 | - 이는 SpringFramework 내부에서 쓰고자 만든것으로, 만약 Utils성격의 클래스를 쓰고 싶다면 Apache Commons StringUtils를 사용해야 한다. 1241 | 1242 | ### 18. Spring Annotation의 적절한 사용 1243 | 1244 | - @Component는 일반적인 스프링 빈으로 등록된(관리하는) 컴포넌트를 의미한다. 1245 | - @Service @Controller, @Repository 등은 Component 임과 동시에 세부적인 기능을 구분하는 명시를 위한 어노테이션이다. 1246 | - @Controller는 Presentation Layer를 담당하고, @Service는 서비스의 로직, @Repository 어노테이션은 DB에서 동작하는 Persistence 레이어를 표시한다. 1247 | 1248 | 기능 역할을 명확하게 하는 레이어라면 Component 보다는 구체적인 스테레오 타입인 하위 어노테이션을 권장한다. 1249 | 1250 | # DTO (Converter vs Mapper) 1251 | 1252 | 1253 | 1254 | 실제 어플리케이션에서는 Entity 로 조회한 DB의 객체를 비즈니스 논리 계층에서 사용하기 위해 DTO에 매핑되어야 하는데 이를 치환하기 위해 수많은 DTO의 Setter, Getter 중복이 발생하게 된다. 데이터베이스의 테이블인 Entity들을 바로 API의 결과로 노출하면 안된다. 1255 | 1256 | 이를 해결 하기 위해 DTO 레이어를 두어야 하며, 이 DTO들의 Converter 를 작성하여 리턴해주는 패턴으로 개발을 해야 한다. 1257 | 1258 | 양방향이 아닌 단방향 변환의 경우에는 Mapper (한 개체의 속성을 다른 개체로 복사할 경우) 라고 명명하여 사용한다. 1259 | 1260 | Converter 패턴의 목적은 해당 형식 간에 일반적인 양방향 변환 방법을 제공하여 깔끔한 구현을 허용하는 것이다. Converter 패턴을 통해 양방향 매핑을 변환함으로써 기존 코드들의 쓸데없는 Setter/Getter 반복 작업을 혁신적으로 줄일 수 있다. 1261 | 1262 | 아래의 예제를 참고하자. 1263 | 1264 | ```java 1265 | public class Converter { 1266 | 1267 | private final Function fromDto; 1268 | private final Function fromEntity; 1269 | 1270 | public Converter(final Function fromDto, final Function fromEntity) { 1271 | this.fromDto = fromDto; 1272 | this.fromEntity = fromEntity; 1273 | } 1274 | 1275 | public final U convertFromDto(final T dto) { 1276 | return fromDto.apply(dto); 1277 | } 1278 | 1279 | public final T convertFromEntity(final U entity) { 1280 | return fromEntity.apply(entity); 1281 | } 1282 | 1283 | public final List createFromDtos(final Collection dtos) { 1284 | return dtos.stream().map(this::convertFromDto).collect(Collectors.toList()); 1285 | } 1286 | 1287 | public final List createFromEntities(final Collection entities) { 1288 | return entities.stream().map(this::convertFromEntity).collect(Collectors.toList()); 1289 | } 1290 | } 1291 | ``` 1292 | 1293 | 변환 하고자 하는 대상 객체는 위의 기본 Converter 제네릭을 상속하여 구현한다. 1294 | 1295 | ```java 1296 | public class UserConverter extends Converter { 1297 | 1298 | public UserConverter() { 1299 | super(UserConverter::convertToEntity, UserConverter::convertToDto); 1300 | } 1301 | 1302 | private static UserDto convertToDto(User user) { 1303 | return new UserDto(user.getFirstName(), user.getLastName(), user.isActive(), user.getUserId()); 1304 | } 1305 | 1306 | private static User convertToEntity(UserDto dto) { 1307 | return new User(dto.getFirstName(), dto.getLastName(), dto.isActive(), dto.getEmail()); 1308 | } 1309 | 1310 | } 1311 | ``` 1312 | 1313 | UserDto 와 User Entity 의 변환은 아래와 같이 간단하게 처리 할 수 있다. 1314 | 1315 | ```java 1316 | var userConverter = new UserConverter(); 1317 | var dtoUser = new UserDto("John", "Doe", true, "whatever[at]wherever.com"); 1318 | var user = userConverter.convertFromDto(dtoUser); 1319 | ``` 1320 | 1321 | ![그림 1-2](./converter.png) 1322 | 1323 | 그림 2 1324 | 1325 | UserConvert 를 통해 dto와 entity 사이의 양방향 변환을 좀 더 명확하고 심플하게 처리할 수 있다. 1326 | 1327 | 제너릭 코드의 확장성에 대해서 굉장히 유연한 코드들을 작성할 수 있게 되므로써 Converter 코드들도 라이브러리등을 통해 코드를 자동으로 변환하게 만들수도 있다. 1328 | 1329 | 대표적으로 ModelMapper (참고 링크 : [https://modelmapper.org/getting-started/](https://modelmapper.org/getting-started/))가 유용한데, 아래와 같이 제너릭으로 확장해서 클래스간의 변환을 간편하게 적용할 수 있다. 1330 | 1331 | ```java 1332 | /** 1333 | * @author CodeVillains 1334 | */ 1335 | @Component 1336 | public class EntityConverter { 1337 | private final ModelMapper modelMapper; 1338 | public EntityConverter(ModelMapper modelMapper) { 1339 | this.modelMapper = modelMapper; 1340 | } 1341 | 1342 | public D convertToDto(S entity, Class dto) { 1343 | return modelMapper.map(entity, dto); 1344 | } 1345 | 1346 | public D convertToEntity(S dto, Class entity) { 1347 | return modelMapper.map(dto, entity); 1348 | } 1349 | 1350 | } 1351 | ``` 1352 | 1353 | 이 코드를 호출하는 부분에서 아래와 같이 사용할 수 있다. 1354 | 1355 | ```java 1356 | private final GoalService goalService; 1357 | private final EntityConverter entityConverter; 1358 | ... 1359 | 1360 | public GoalResponse findById(Long id) { 1361 | Goal goal = goalService.findById(id).orElseThrow(() -> new GoalNotFoundException(id)); 1362 | GoalResponse response = entityConverter.convertToDto(goal, GoalResponse.class); // entity to dto 1363 | return response; 1364 | } 1365 | ``` 1366 | 1367 | Converter 클래스보다 ModelMapper 라이브러리를 이용한 EntityConverter 가 코드상으로 훨씬 이해하기 편할 것이다. 1368 | 1369 | 만약 List 타입의 오브젝트 매핑을 하고자 한다면 아래와 같이 변환 코드를 만들어서 사용할 수 있다. 1370 | 1371 | ```java 1372 | public List convertListToDto(List sourceList, Class destinationType) { 1373 | return sourceList.stream() 1374 | .map(source -> modelMapper.map(source, destinationType)) 1375 | .collect(Collectors.toList()); 1376 | } 1377 | 1378 | public List convertListToEntity(List sourceList, Class destinationType) { 1379 | return sourceList.stream() 1380 | .map(source -> modelMapper.map(source, destinationType)) 1381 | .collect(Collectors.toList()); 1382 | } 1383 | ``` 1384 | 1385 | 실제 호출하는 부분의 코드는 아래와 같다. 1386 | 1387 | ```java 1388 | List dtoList = converter.convertListToDto(entityList, DtoClass.class); 1389 | for (DtoClass dto : dtoList) { 1390 | System.out.println(dto.getId()); 1391 | System.out.println(dto.getName()); 1392 | } 1393 | 1394 | List convertedEntityList = converter.convertListToEntity(dtoList, EntityClass.class); 1395 | for (EntityClass entity : convertedEntityList) { 1396 | System.out.println(entity.getId()); 1397 | System.out.println(entity.getName()); 1398 | } 1399 | ``` 1400 | 1401 | -------------------------------------------------------------------------------- /book_600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/villainscode/coding-guide/eafaa0b93d2dd279e8035798d6630b850e666640/book_600.png -------------------------------------------------------------------------------- /converter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/villainscode/coding-guide/eafaa0b93d2dd279e8035798d6630b850e666640/converter.png -------------------------------------------------------------------------------- /src/main/java/net/mingleup/guide/MainApp.java: -------------------------------------------------------------------------------- 1 | package net.mingleup.guide; 2 | 3 | /** 4 | * @author CodeVillains 5 | * @description : 6 | */ 7 | public class MainApp { 8 | 9 | public static void main(String[] args) { 10 | System.out.println("hello world"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/net/mingleup/guide/employee/EmployeePaySwitch.java: -------------------------------------------------------------------------------- 1 | package net.mingleup.guide.employee; 2 | 3 | /** 4 | * @author CodeVillains 5 | * @description 장점 : switch 구문을 이용해서 로직을 직관적으로 이해할 수 있다. 단순한 로직의 경우 직접 구현하는 형태로 초보자용 코드로 적합하다. 단점 : 코드 중복 - switch 문 내의 각 사례가 서로 다른 계산을 처리하여 중복이 발생하므로 6 | * 일정 수준의 코드 중복이 발생한다. OCP 위반 - 직원 유형이 추가된다거나 급여 계산식이 바뀔 경우 스위치 구문내부가 급격하게 늘어나고 각각의 코드 로직이 복잡해져 유지관리가 어렵다.(개방폐쇄 원칙 위반) 7 | */ 8 | public class EmployeePaySwitch { 9 | 10 | 11 | public static void main(String[] args) { 12 | String employeeType = "Contract"; // 직원 유형 설정 13 | double hoursWorked = 40; // 근무 시간 14 | double hourlyRate = 15000; // 시간당 급여 15 | 16 | double pay = calculatePay(employeeType, hoursWorked, hourlyRate); 17 | System.out.println("직원 유형: " + employeeType); 18 | System.out.println("근무 시간: " + hoursWorked + "시간"); 19 | System.out.println("시간당 급여: " + hourlyRate + "원"); 20 | System.out.println("총 급여: " + pay + "원"); 21 | } 22 | 23 | public static double calculatePay(String employeeType, double hoursWorked, double hourlyRate) { 24 | double pay = 0; 25 | 26 | switch (employeeType) { 27 | case "Regular": 28 | pay = hoursWorked * hourlyRate + 50000; // 정규직 추가 급여 29 | break; 30 | case "Contract": 31 | pay = hoursWorked * hourlyRate; 32 | break; 33 | case "Temporary": 34 | pay = hoursWorked * hourlyRate * 0.8; // 임시직 급여율 35 | break; 36 | case "Asist": 37 | pay = hoursWorked * hourlyRate * 0.7; // 어시스트 급여율 38 | break; 39 | default: 40 | System.out.println("유효하지 않은 직원 유형입니다."); 41 | } 42 | return pay; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/net/mingleup/guide/employee/EmployeePaySwitchRefactor.java: -------------------------------------------------------------------------------- 1 | package net.mingleup.guide.employee; 2 | 3 | /** 4 | * @author CodeVillains 5 | * @description : switch 구문 내에서 로직이 들어가는 부분을 공통 메서드로 분리하였다. 6 | * 장점 : 공통 급여 계산 로직을 별도의 메서드로 추출하면 중복이 줄어들고 코드 재사용성이 좋아졌다. 각 계산 로직이 메서드 호출 형태로 변경 되어 가독성 및 변경 7 | * 영향도가 줄어들어 유지보수가 더 쉽다. 8 | * 단점 : 아직 switch 문에 로직이 존재하여 수정이 필요할 수 있고 (OCP) switch 문이 주요 흐름이므로 확장성에는 완전히 유용해보이지 않는다. 9 | */ 10 | public class EmployeePaySwitchRefactor { 11 | 12 | 13 | public static void main(String[] args) { 14 | String employeeType = "Contract"; // 직원 유형 설정 15 | double hoursWorked = 40; // 근무 시간 16 | double hourlyRate = 15000; // 시간당 급여 17 | 18 | double pay = calculatePay(employeeType, hoursWorked, hourlyRate); 19 | System.out.println("직원 유형: " + employeeType); 20 | System.out.println("근무 시간: " + hoursWorked + "시간"); 21 | System.out.println("시간당 급여: " + hourlyRate + "원"); 22 | System.out.println("총 급여: " + pay + "원"); 23 | } 24 | 25 | public static double calculatePay(String employeeType, double hoursWorked, double hourlyRate) { 26 | double pay = 0; 27 | 28 | switch (employeeType) { 29 | case "Regular": 30 | pay = calculatePayWithAdjustment(hoursWorked, hourlyRate, 1.0); // 정규직 추가 급여 31 | break; 32 | case "Contract": 33 | pay = calculatePayWithAdjustment(hoursWorked, hourlyRate, 1.0); 34 | break; 35 | case "Temporary": 36 | pay = calculatePayWithAdjustment(hoursWorked, hourlyRate, 0.8); // 임시직 급여율 37 | break; 38 | case "Assist": 39 | pay = calculatePayWithAdjustment(hoursWorked, hourlyRate, 0.7); // 어시스트 급여율 40 | break; 41 | default: 42 | System.out.println("유효하지 않은 직원 유형입니다."); 43 | } 44 | 45 | return pay; 46 | } 47 | 48 | private static double calculatePayWithAdjustment(double hoursWorked, double hourlyRate, double adjustment) { 49 | return hoursWorked * hourlyRate * adjustment; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/net/mingleup/guide/employee/abstractfactory/AssistPayCalculator.java: -------------------------------------------------------------------------------- 1 | package net.mingleup.guide.employee.abstractfactory; 2 | 3 | /** 4 | * @author CodeVillains 5 | * @description : 6 | */ 7 | class AssistPayCalculator implements PayCalculator { 8 | @Override 9 | public double calculatePay(double hoursWorked, double hourlyRate) { 10 | return hoursWorked * hourlyRate * 0.7; // Assist employee 11 | } 12 | } -------------------------------------------------------------------------------- /src/main/java/net/mingleup/guide/employee/abstractfactory/ContractPayCalculator.java: -------------------------------------------------------------------------------- 1 | package net.mingleup.guide.employee.abstractfactory; 2 | 3 | /** 4 | * @author CodeVillains 5 | * @description : 6 | */ 7 | public class ContractPayCalculator implements PayCalculator { 8 | @Override 9 | public double calculatePay(double hoursWorked, double hourlyRate) { 10 | return hoursWorked * hourlyRate; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/net/mingleup/guide/employee/abstractfactory/EmployeePayAbstractFatoryMain.java: -------------------------------------------------------------------------------- 1 | package net.mingleup.guide.employee.abstractfactory; 2 | 3 | /** 4 | * @author CodeVillains 5 | * @description : 6 | */ 7 | public class EmployeePayAbstractFatoryMain { 8 | 9 | public static void main(String[] args) { 10 | String employeeType = "Contract"; // Employee type setting 11 | double hoursWorked = 40; // Hours worked 12 | double hourlyRate = 15000; // Hourly rate 13 | 14 | PayCalculator payCalculator = PayCalculatorFactory.createPayCalculator(employeeType); 15 | if (payCalculator != null) { 16 | double pay = payCalculator.calculatePay(hoursWorked, hourlyRate); 17 | System.out.println("Employee type: " + employeeType); 18 | System.out.println("Hours worked: " + hoursWorked + " hours"); 19 | System.out.println("Hourly rate: " + hourlyRate + " won"); 20 | System.out.println("Total pay: " + pay + " won"); 21 | } else { 22 | System.out.println("Invalid employee type."); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/java/net/mingleup/guide/employee/abstractfactory/PayCalculator.java: -------------------------------------------------------------------------------- 1 | package net.mingleup.guide.employee.abstractfactory; 2 | 3 | /** 4 | * @author CodeVillains 5 | * @description : 급여 계산에 대한 공통 인터페이스 정의 6 | */ 7 | public interface PayCalculator { 8 | double calculatePay(double hoursWorked, double hourlyRate); 9 | } -------------------------------------------------------------------------------- /src/main/java/net/mingleup/guide/employee/abstractfactory/PayCalculatorFactory.java: -------------------------------------------------------------------------------- 1 | package net.mingleup.guide.employee.abstractfactory; 2 | 3 | /** 4 | * @author CodeVillains 5 | * @description : 6 | */ 7 | public class PayCalculatorFactory { 8 | 9 | static PayCalculator createPayCalculator(String employeeType) { 10 | switch (employeeType) { 11 | case "Regular": 12 | return new RegularPayCalculator(); 13 | case "Contract": 14 | return new ContractPayCalculator(); 15 | case "Temporary": 16 | return new TemporaryPayCalculator(); 17 | case "Assist": 18 | return new AssistPayCalculator(); 19 | default: 20 | return null; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/net/mingleup/guide/employee/abstractfactory/RegularPayCalculator.java: -------------------------------------------------------------------------------- 1 | package net.mingleup.guide.employee.abstractfactory; 2 | 3 | /** 4 | * @author CodeVillains 5 | * @description : 6 | */ 7 | public class RegularPayCalculator implements PayCalculator { 8 | 9 | @Override 10 | public double calculatePay(double hoursWorked, double hourlyRate) { 11 | return hoursWorked * hourlyRate + 50000; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/net/mingleup/guide/employee/abstractfactory/TemporaryPayCalculator.java: -------------------------------------------------------------------------------- 1 | package net.mingleup.guide.employee.abstractfactory; 2 | 3 | /** 4 | * @author CodeVillains 5 | * @description : 6 | */ 7 | public class TemporaryPayCalculator implements PayCalculator { 8 | @Override 9 | public double calculatePay(double hoursWorked, double hourlyRate) { 10 | return hoursWorked * hourlyRate * 0.8; // Temporary employe 11 | } 12 | } -------------------------------------------------------------------------------- /src/main/java/net/mingleup/guide/employee/strategy/AssistPayCalculator.java: -------------------------------------------------------------------------------- 1 | package net.mingleup.guide.employee.strategy; 2 | 3 | /** 4 | * @author CodeVillains 5 | * @description : 6 | */ 7 | public class AssistPayCalculator implements PayCalculator { 8 | @Override 9 | public double calculatePay(double hoursWorked, double hourlyRate) { 10 | return hoursWorked * hourlyRate * 0.7; // Assist employee 11 | } 12 | } -------------------------------------------------------------------------------- /src/main/java/net/mingleup/guide/employee/strategy/ContractPayCalculator.java: -------------------------------------------------------------------------------- 1 | package net.mingleup.guide.employee.strategy; 2 | 3 | /** 4 | * @author CodeVillains 5 | * @description : 6 | */ 7 | public class ContractPayCalculator implements PayCalculator { 8 | @Override 9 | public double calculatePay(double hoursWorked, double hourlyRate) { 10 | return hoursWorked * hourlyRate; 11 | } 12 | } -------------------------------------------------------------------------------- /src/main/java/net/mingleup/guide/employee/strategy/EmployeePayStrategyMain.java: -------------------------------------------------------------------------------- 1 | package net.mingleup.guide.employee.strategy; 2 | 3 | /** 4 | * @author CodeVillains 5 | * @description : 6 | */ 7 | public class EmployeePayStrategyMain { 8 | public static void main(String[] args) { 9 | String employeeType = "Contract"; 10 | double hoursWorked = 40; 11 | double hourlyRate = 15000; 12 | 13 | // factory를 통해 지정된 유형의 인스턴스를 얻는다. 14 | PayCalculator payCalculator = PayCalculatorFactory.getPayCalculator(employeeType); 15 | // 해당 인스턴스의 계산 로직을 호출 16 | double pay = payCalculator.calculatePay(hoursWorked, hourlyRate); 17 | 18 | System.out.println("직원 유형: " + employeeType); 19 | System.out.println("근무 시간: " + hoursWorked + "시간"); 20 | System.out.println("시간당 급여: " + hourlyRate + "원"); 21 | System.out.println("총 급여: " + pay + "원"); 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/java/net/mingleup/guide/employee/strategy/PayCalculator.java: -------------------------------------------------------------------------------- 1 | package net.mingleup.guide.employee.strategy; 2 | 3 | /** 4 | * @author CodeVillains 5 | * @description : 급여 계산에 대한 공통 인터페이스 정의 6 | */ 7 | public interface PayCalculator { 8 | double calculatePay(double hoursWorked, double hourlyRate); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/net/mingleup/guide/employee/strategy/PayCalculatorFactory.java: -------------------------------------------------------------------------------- 1 | package net.mingleup.guide.employee.strategy; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * @author CodeVillains 8 | * @description : 9 | */ 10 | public class PayCalculatorFactory { 11 | private static final Map calculatorMap = new HashMap<>(); 12 | 13 | static { 14 | // 각 계산기 인스턴스 15 | calculatorMap.put("Regular", new RegularPayCalculator()); 16 | calculatorMap.put("Contract", new ContractPayCalculator()); 17 | calculatorMap.put("Temporary", new TemporaryPayCalculator()); 18 | calculatorMap.put("Assist", new AssistPayCalculator()); 19 | } 20 | 21 | static PayCalculator getPayCalculator(String employeeType) { 22 | return calculatorMap.getOrDefault(employeeType, (hoursWorked, hourlyRate) -> { 23 | System.out.println("유효하지 않은 직원 유형입니다."); 24 | return 0.0; 25 | }); 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/java/net/mingleup/guide/employee/strategy/RegularPayCalculator.java: -------------------------------------------------------------------------------- 1 | package net.mingleup.guide.employee.strategy; 2 | 3 | /** 4 | * @author CodeVillains 5 | * @description : 6 | */ 7 | public class RegularPayCalculator implements PayCalculator { 8 | @Override 9 | public double calculatePay(double hoursWorked, double hourlyRate) { 10 | return hoursWorked * hourlyRate + 50000; // regula employee 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/net/mingleup/guide/employee/strategy/TemporaryPayCalculator.java: -------------------------------------------------------------------------------- 1 | package net.mingleup.guide.employee.strategy; 2 | 3 | /** 4 | * @author CodeVillains 5 | * @description : 6 | */ 7 | public class TemporaryPayCalculator implements PayCalculator { 8 | @Override 9 | public double calculatePay(double hoursWorked, double hourlyRate) { 10 | return hoursWorked * hourlyRate * 0.8; // Temporary employee 11 | } 12 | } --------------------------------------------------------------------------------