ํ๋น๋ฏธ๋์ด์ ๋๋๋ฆฌ๋ทฐ์ด๋ค 2024๋ฅผ ํตํด 8์, ์ค๋ฌด๋ก ํตํ๋ ํด๋ฆฐ ์ฝ๋ (Clean Code Cookbook) ๋์๋ฅผ ์ ๊ณต ๋ฐ์ ์ฝ์ด๋ณด๊ฒ ๋์์ต๋๋ค.
์ฑ ์ ์ฝ 500p ์ ๋์ ๋ถ๋์ผ๋ก ์์ ํธ์ ์๋๊ณ ์, ํด๋ฆฐ ์ฝ๋์ ๋ํด ๊ด์ฌ์ด ๋ง๊ธฐ๋ ํ๊ณ , ์์ ์ ์ฝ์๋ ํด๋ฆฐ ์ฝ๋ ์๊ฐ๋ ๋์ ์ฝ์ด๋ณด๊ฒ ๋์๋ค์. ์ ๋ถ ์ฝ์ง๋ ๋ชปํ๊ณ , 60%์ ๋๋ง ์ฝ์ ์ํ์์ ๋ ์ฝ์ผ๋ฉด์ ๋ด์ฉ์ด ์ถ๊ฐ๋ ์ ์์ต๋๋ค.
๊ฒฐ๋ก ๋ถํฐ ๋ง์๋๋ฆฌ๋ฉด ํด๋ฆฐ ์ฝ๋ (Clean Code) ๋์์ ๋น์ทํ ๋ด์ฉ๋ค์ด ๋ง์์ด์ ํด๋ฆฐ ์ฝ๋๋ฅผ ์ฝ์๋ค๋ฉด, ๊ตณ์ด ์ด ์ฑ ์ ๋ณด์ง๋ ์์๋ ๋ ๊ฒ ๊ฐ์ต๋๋ค..(?)
2์ฅ ๊ณต๋ฆฌ ์ค์
38p, ์ํํธ์จ์ด๋ ์๋ฎฌ๋ ์ดํฐ๋ฅผ ๊ตฌ์ถํ๋ ๊ฒ๊ณผ ๊ฐ์ผ๋ฉฐ MAPPER (๋งคํผ) ๋ผ๋ ์ฝ์ด๋ก ํํํฉ๋๋ค.
MAPPER (Model: Abstract Partial and Programmable Explaining Reality), ๋ชจ๋ธ์ ๋ถ๋ถ์ ์ถ์ํ์ ํ๋ก๊ทธ๋๋ฐ์ ํตํด ํ์ค์ ๋ฌ์ฌํด์ผ ํจ
MAPPER ๋ผ๋ ์ฉ์ด๊ฐ ์ฑ ์ฌ๊ธฐ ์ ๊ธฐ์ ๋์ค๋๋ฐ์, ์ถ์ํ๋ฅผ ํตํด ํ์ค์ ๋ฌ์ฌํด์ผ ํ๋ค๋ ๊ฐ๋ ์ ๋๋ค.
3์ฅ ๋น์ฝํ ๋ชจ๋ธ
51p, ๊ฐ์ฒด๋ฅผ ๋์์ธํ ๋ ๋ฐ์ดํฐ๋ง ๋ชจ๋ธ๋งํ๋ ๋น์ฝํ ๊ฐ์ฒด๋ฅผ ๋ง๋ค๊ธฐ๋ณด๋ค๋, ๊ฐ์ฒด์ ๋์์ ๊ธฐ๋ฐ์ผ๋ก ๊ฐ์ฒด๋ฅผ ๋์์ธํด์ผ ํฉ๋๋ค
// ...
private List<Integer> data;
public List<Integer> getData() {
return data; // ์บก์ํ ์๋ฐ
}
getData() ๋ฉ์๋๋ ๋ด๋ถ ๋ฐ์ดํฐ ์ปฌ๋ ์ ์ ๋ณต์ฌ๋ณธ์ ๋ง๋๋ ๋์ ๋ด๋ถ ๋ฐ์ดํฐ ์ปฌ๋ ์ ์ ๋ํ ์ฐธ์กฐ๋ฅผ ๋ฐํํ์ฌ, ์ธ๋ถ์์ ์ปฌ๋ ์ ์ ๋ณ๊ฒฝํ๋ฉด ๋ด๋ถ ๋ฐ์ดํฐ์ ์ง์ ๋ฐ์๋์ด ์๊ธฐ์น ์์ ๋์, ๊ฒฐํจ์ด ๋ฐ์ํ ์ ์๋ค.
4์ฅ ๊ธฐ๋ณธํ ์ง์ฐฉ
72p, ์์ ๊ฐ์ฒด ์์ฑํ๊ธฐ (VO, Value Object)
// As-Is
public Class Person {
private final String name;
...
}
// To-Be
public class Person {
private final Name name;
...
}
VO ๊ฐ์ฒด๋ฅผ ์ ๊ทน์ ์ผ๋ก ์ฌ์ฉํด๋ณด๊ธฐ๋ ํ๊ณ , ์ฌ์ฉํ์ง ์์ ์ผ์ด์ค๋ ์์ง๋ง, ๊ฐ๊ฐ์ ์ฅ๋จ์ ๋ค์ด ์กด์ฌํ๋ ๊ฒ ๊ฐ์ต๋๋ค.
6์ฅ ์ ์ธ์ ์ฝ๋
111p, ๋ณ์, ๋ฉ์๋, ํด๋์ค์ ๋ค์ด๋ฐ์ ํญ์ ๊ธ์ ์ ์ธ ๋ค์ด๋ฐ์ผ๋ก ์ค์ ํ์ธ์ (๊ฐ๋ ์ฑ)
https://refactoring.com/catalog/removeDoubleNegative.html
// Bad
if (!work.isNotFinished()) ...
// Good
if (work.isDone()) ...
๊ฐ์ธ์ ์ผ๋ก ๊ณต๊ฐํ๋ ๊ท์น์ธ๋ฐ์, ์ด์ค ๋ถ์ ์ ๋ค์ด๋ฐ์ ํ ๋ฒ ๋ ์๊ฐํด์ผ ํ๋ ๋ฒ๊ฑฐ๋ก์์ด ๋ง์๋ ๊ฒ ๊ฐ์ต๋๋ค.
7์ฅ ๋ช ๋ช
134p, ์ด๋ฆ์ ์ด๋ป๊ฒ ์ง์ด์ผ ํ ์ง ๋ชจ๋ฅด๊ฒ ๋ค๋ฉด, ๋ง์ง๋ง ํจ์ ํธ์ถ๊ณผ ๋์ผํ ์ด๋ฆ์ผ๋ก ๋ค์ด๋ฐ
// Bad
var result = calculateAverageSalary();
// Good
var averageSalary = calculateAverageSalary();
๋ชจ๋ ๊ฐ๋ฐ์์ ๊ณ ์ถฉ = ๋ค์ด๋ฐ
151p, data ๋ช ์นญ ํผํ๊ธฐ
data๊ฐ ํฌํจ๋ ์ด๋ฆ์ ์ฌ์ฉํ๋ฉด ๊ฐ์ฒด๊ฐ ๋น์ฝํด์ง๋ฏ๋ก ๋๋ฉ์ธ ๋ฐ ์ญํ ์ ๋ํ๋ด๋ ์ด๋ฆ์ ๊ณ ๋ คํด์ผ ํฉ๋๋ค.
๋ณ์์ ์ด๋ฆ์ ํด๋น ๋ณ์์ ๋๋ฉ์ธ๊ณผ ์ด์ ์ํํ๋ ์ญํ ์ ๋ฐ์ํด์ผ ํฉ๋๋ค.
// Bad
if (!dataExists()) ...
// Good
if (!peopleFound()) ...
data... xxxdata ๋ฑ์ ๋ค์ด๋ฐ์ ๋ ๊ฑฐ์ ์ฝ๋์์ ๊ต์ฅํ ๋ง์ด ๋ด์์๋ ๋ค์ด๋ฐ์ด์ด์ ๋์ฑ ๊ณต๊ฐ์ด ๊ฐ๋ ๊ฒ ๊ฐ๋ค์.. ๐
8์ฅ ์ฃผ์
164p, ์ฃผ์์ ํ ์คํธ๋ก ๋์ฒดํ๊ธฐ
๋ฌธ์ : ํจ์๊ฐ ์ํํ๋ ์์ ์ ์ค๋ช ํ๋ ์ฃผ์์ด ์๋๋ฐ, ์ ์ ์ด๊ณ ์ค๋๋ ์ค๋ช ๋์ ๋์ ์ด๊ณ ์ ์ง ๊ด๋ฆฌ๊ฐ ์ ๋๋ ๋ฌธ์ํ๋ฅผ ์ํฉ๋๋ค.
-> ์ฃผ์์ ๊ฐ์ ธ์์ ํจ์ถ์์ผ ํจ์๋ช ์ผ๋ก ์ฌ์ฉํ๊ณ , ์ฝ๋๋ฅผ ํ ์คํธํ ๋ค ์ฃผ์์ ์ ๊ฑฐํ์ธ์.
def multiply(a, b):
# ๋ ์ซ์ ๊ณฑํ ๊ฒฐ๊ณผ
# ์ซ์ ์ค ํ๋๊ฐ 0์ด๋ฉด ๊ฒฐ๊ณผ๋ 0
# ๋ ์ซ์๊ฐ ๋ชจ๋ ์์์ด๋ฉด ๊ฒฐ๊ณผ๋ ์์
# ๋ ์ซ์๊ฐ ๋ชจ๋ ์์์ด๋ฉด ๊ฒฐ๊ณผ๋ ์์
return a * b
# Test
class TestMultiply(unittest.TestCase):
def test_multiply_both_possitive_outcome_is_possitive ...
def test_multiply_both_negative_outcome_is_positive
def test_multiply_first_is_zero_outcome_is_zero ...
def test_multiply_second_is_zero_outcome_is_zero ...
๋ฌด์๋ฏธํ๊ณ ๋ถํ์ํ ์ฃผ์์ ์ง์ํ์ง๋ง, ์ฃผ์์ ๊ฒฐ๊ณผ๋ฌผ์ ๋ํด ํ ์คํธ ์ฝ๋๋ก ์ ์งํ๋ ๋ฐฉ๋ฒ๋ ์๊ฒ ๋ค์.
9์ฅ ํ์ค
๋๊ท๋ชจ ์กฐ์ง์์๋ ์๋ก ๋ค๋ฅธ ํ๊ณผ ๊ฐ๋ฐ์๊ฐ ๊ณตํต์ ๊ท์น๊ณผ ๋ชจ๋ฒ ์ฌ๋ก์ ๋ฐ๋ผ ์์ ํ ์ ์๋๋ก ์ฝ๋ ๊ท์น์ ๋ง๋ จํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. → ์ฝ๋ฉ ํ์ค ์ ์ฉ
10์ฅ ๋ณต์ก์ฑ
๋ณต์ก์ฑ์ ์ฒ๋ฆฌํ๋ ๋ฐ ๊ฐ์ฅ ํจ๊ณผ์ ์ธ ๋๊ตฌ๋ ์ถ์ํ์ ๋๋ค. ์บก์ํ๋ ๊ฐ์ฒด ์งํฅ ํ๋ก๊ทธ๋๋ฐ์์ ๋ณต์ก์ฑ์ ๊ด๋ฆฌํ๋ ์ฃผ์ ์ถ์ํ ๋ฐฉ๋ฒ
๋ณต์ก์ฑ์ ๋ชจ๋ ๋๊ท๋ชจ ์ํํธ์จ์ด ์์คํ ์ ์กด์ฌํ๋ฉฐ ์ข ์ข ๋ฌธ์ ์ ์ฃผ์ ์์ธ์ด ๋ฉ๋๋ค. → ์ฐ๋ฐ์ ์ธ ๋ณต์ก์ฑ์ ๊ฐ๋ฅํ ํ ๊ฐ์ฅ ๋ฎ์ ์์ค์ผ๋ก ์ ์งํด์ผ ํฉ๋๋ค.
๋ณต์ก์ฑ์ ์ ๊ด๋ฆฌํ๋ ๊ฒ์ด ๊ต์ฅํ ์ค์ํ๋ค๊ณ ์๊ฐํ๋๋ฐ์, ์ฑ ์ ๋ด์ฉ๋ค์ ํด๋ฆฐ ์ฝ๋์ ๊ฑฐ์ ๋น์ทํด์ ๋ ์์ฑํ์ง๋ ์์์ต๋๋ค.
(๋ฐ๋ณต ์ฝ๋ ์ ๊ฑฐ, ์์ฑ์ผ๋ก ์ํ ๋ณ๊ฒฝํ๊ธฐ, ์ฝ๋์์ ๊ต๋ฌํจ ์ ๊ฑฐํ๊ธฐ, ๋ฉ์๋๋ฅผ ๊ฐ์ฒด๋ก ์ถ์ถํ๊ธฐ ๋ฑ๋ฑ)
11์ฅ ๋ธ๋กํฐ
205p, ๊ณผ๋ํ ๋ฉ์๋ ์ ๊ฑฐํ๊ธฐ
ํด๋์ค๋ฅผ ๋ณด๋ค ์์ง๋ ฅ์๋ ์์ ์กฐ๊ฐ์ผ๋ก ๋๋์ธ์.
// Bad
public class MyHelperClass {
public void print() ...
public void format() ...
public vod persist() ...
...
}
// Good
public class Printer {
public void print() ...
}
public class DateToStringFormatter {
public void format() ...
}
...
Util ์ฑ๊ฒฉ์ ํด๋์ค๋ค์ ๋ํ ์์ ์ด์ง๋ง, ํํ ์ฌ์ฉํ๋ ์คํ๋ง์์๋ ์ ์ฉํ ์ ์์ ๊ฒ ๊ฐ์ต๋๋ค. (API, ์ปจํธ๋กค๋ฌ ๋ถ๋ฆฌ)
13์ฅ ๋น ๋ฅธ ์คํจ
231p, switch ๋ฌธ์์ ๊ธฐ๋ณธ๊ฐ ์ ๊ฑฐํ๊ธฐ
case ๋ฌธ์ default ๋ฅผ ์ถ๊ฐํ์ง ๋ง๊ณ , ์์ธ๋ฅผ ๋ฐ์์ํค๋ ๋ฐฉ์์ผ๋ก ์์ฑํ์ธ์. ์ด๋ ์ฝ๋๊ฐ ๋ ๋ช ์์ ์ด๋ฉฐ, ์ถ์ธก์ ์ํ ํด๊ฒฐ์ฑ ์ ์ฌ์ฉํ์ง ์๋๋ก ํฉ๋๋ค.
default(๊ธฐ๋ณธ๊ฐ)๋ ‘์์ง ์์ง ๋ชปํ๋ ๋ชจ๋ ๊ฒ’์ ์๋ฏธํฉ๋๋ค. ๋ฏธ๋๋ ์์ธกํ ์ ์์ผ๋ฏ๋ก ‘์์์น ๋ชปํ ๊ฒฝ์ฐ’์ ๋ํด ๊ธฐ๋ณธ๊ฐ์ ์์ธ ์ฒ๋ฆฌ๊ฐ ๋์ด์ผ ํฉ๋๋ค.
14์ฅ if ๋ฌธ
240p, ์ฐ๋ฐ์ if ๋ฌธ์ ๋คํ์ฑ์ผ๋ก ๋์ฒดํ๊ธฐ
ํ์๋ก ์๋ณ๋๋ if๋ฌธ, ์ฐ๋ฐ์ ์ธ if๋ฌธ์ ๋ํ ๊ตฌ๋ถ
// Bad
public class Movie {
private String rate;
public Movie(String rate) {
this.rate = rate;
}
public String getRate() {
return rate;
}
}
public class MovieWatcher {
private final int age;
public MovieWatcher(int age) {
this.age = age;
}
public void watchMovie(Movie movie) {
// ์ฐ๋ฐ์ ์ธ (๊ฒฐํฉ๋) ๋ถ๊ธฐ์ฒ๋ฆฌ -> ๋ฌธ์์ด๋ก ๋ฑ๊ธ์ ๋ชจ๋ธ๋งํ๊ธฐ๋ก ํ ์ค๊ณ๊ฐ ๋ฌธ์ ๋ผ๊ณ ํ๋ค.
// ์ด๋ ํ์ฅ์ ์ํด ๊ฐ๋ฐฉ์ ์ด์ง๋ ์๊ณ , ์์ ์ ์ํด ํ์์ ์ด์ง๋ ์๋ค.
if ((this.age < 18) && movie.getRate().equals("์ฑ์ธ ์ ์ฉ")) {
throw new IllegalStateException("์ด ์ํ๋ฅผ ์์ฒญํ ์ ์์ต๋๋ค.");
} else if ((this.age < 13) && movie.getRate().equals("13์ธ ์ ์ฉ")) {
throw new IllegalStateException("์ด ์ํ๋ฅผ ์์ฒญํ ์ ์์ต๋๋ค.");
}
// else if, else if ...
// ์๋ก์ด ๋ฑ๊ธ์ด ์๊ธธ ๋๋ง๋ค ๋ ๋ค๋ฅธ if ๋ฌธ์ด ํ์ํ๋ค.
playMovie();
}
private void playMovie() {
}
public static void main(String[] args) {
final var jane = new MovieWatcher(12);
final var theExorcist = new Movie("์ฑ์ธ ์ ์ฉ");
// ์ ์ธ์ 12์ด์ด์ด์ ์ํ <์์์ํธ์ค>๋ฅผ ์์ฒญํ ์ ์์ต๋๋ค.
jane.watchMovie(theExorcist);
}
}
// Good
public interface MovieRate {
// ์ฑ์ธ ์ ์ฉ, 13์ธ ์ ์ฉ ๋ฑ์ ๋ถ๊ธฐ ์ฒ๋ฆฌ์ ๋ํ ๊ตฌํ์ฒด
void warnIfNotAllowed(int age);
}
public class AdultsOnlyMovieRate implements MovieRate {
// 1. ๋ชจ๋ if ๋ฌธ์ ์ถ์ํ๋ก ์ด๋ํ์ธ์.
@Override
public void warnIfNotAllowed(int age) {
if (age < 18) {
throw new IllegalStateException("์ด ์ํ๋ฅผ ์์ฒญํ ์ ์์ต๋๋ค.");
}
}
}
public class PG13MovieRate implements MovieRate {
// 2. ๋ชจ๋ if ๋ฌธ์ ์ถ์ํ๋ก ์ด๋ํ์ธ์.
@Override
public void warnIfNotAllowed(int age) {
if (age < 13) {
throw new IllegalStateException("์ด ์ํ๋ฅผ ์์ฒญํ ์ ์์ต๋๋ค.");
}
}
}
public class Movie {
private final MovieRate rate;
public Movie(MovieRate rate) {
this.rate = rate;
}
public void checkWatchAllowed(int age) {
rate.warnIfNotAllowed(age);
}
}
public class MovieWatcher {
private final int age;
public MovieWatcher(int age) {
this.age = age;
}
public void watchMovie(Movie movie) {
movie.checkWatchAllowed(this.age);
}
public static void main(String[] args) {
final var theExorcist = new Movie(new AdultsOnlyMovieRate());
final var gremlins = new Movie(new PG13MovieRate());
final var jane = new MovieWatcher(12);
jane.watchMovie(theExorcist); // 12์ด์ด์ด์ ์์ฒญ ๋ถ๊ฐ
jane.watchMovie(gremlins); // 12์ด์ด์ด์ ์์ฒญ ๋ถ๊ฐ
final var joe = new MovieWatcher(16);
joe.watchMovie(theExorcist); // 16์ด์ด์ด์ ์์ฒญ ๋ถ๊ฐ
joe.watchMovie(gremlins); // 16์ด์ด์ด์ ์์ฒญ ๊ฐ๋ฅ
}
}
์ ์์ ์์๋ MovieRate์ ๋ฌธ์์ด ๋น๊ต๊ฐ ์ฐ๋ฐ์ ์ธ if๋ฌธ์ ๋ํ ๋ถ๊ธฐ์ฒ๋ฆฌ๋ผ๊ณ ํ์ฌ, ํ์๋ก ์๋ณ๋๋ if๋ฌธ์ ๊ฒฝ์ฐ ๋์ฒดํ ํ์๊ฐ ์๋ค๊ณ ๋งํ๊ณ ์๋๋ฐ, ์ด๊ฒ์ ๋ํ ์ ํํ ํ๋จ์ด ์ ์์๋ ๊ฒ ๊ฐ์ต๋๋ค.
์ด์๋ ๋ณ๊ฐ๋ก ์์ ๊ฐ์ if, else if ... ๋ฑ์ ์ฝ๋๋ ๊ต์ฅํ ๋ง์ด ์กด์ฌํ๋ ํ์์ด๊ธฐ์ ์ ์ฉํ๊ฒ ํ์ฉํ ์ ์๋ ๋ฐฉ์์ ๋๋ค.
249p, boolean ๋ณ์ ์ฌ๊ตฌ์ฑํ๊ธฐ
boolean ๋ณ์๋ if๋ฅผ ์์ฑํ๊ฒ ํ๋ฏ๋ก ์ฌ์ฉํ์ง ๋ง๊ณ , ๋คํ์ฑ ์ํ๋ฅผ ์์ฑํ์ธ์.
function processBatch(
bool $useLogin,
bool $deleteEntries) {
// ...
}
function processBatch(
LoginStrategy $login,
DeletePolicy $deleteionPolicy) {
// ...
}
boolean ๋ณ์ ์์ฒด๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ์๋๊ธฐ๋ ๊ฒ๋ ํ๋์ ๋ฐฉ๋ฒ์ผ ๊ฒ ๊ฐ์๋ฐ, ์์ ๊ฐ์ด ๊ฐ์ฒด๋ก ์ฌ๊ตฌ์ฑํ๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ธ ๊ฒ ๊ฐ์ต๋๋ค.
256p, ์์์ else ๋ฌธ ์ถ๊ฐํ๊ธฐ
else ๋ฌธ์ด ์๋ if๋ฌธ์ด ์๋๋ฐ, ๋ช ์์ ์ผ๋ก ํํํด์ผ ํฉ๋๋ค.
๋ช ์์ ์ธ else ๋ฌธ์ด ์๋ ์ฝ๋๋ ๊ฐ๋ ์ฑ์ด ๋๊ณ ์ธ์ง ๋ถํ๊ฐ ์ ์ต๋๋ค. ๋ํ ์๊ธฐ์น ์์ ์กฐ๊ฑด์ ๊ฐ์งํ๊ณ ๋น ๋ฅด๊ฒ ์คํจํ๊ธฐ ์์น์ ์ค์ํฉ๋๋ค.
function carBrandImplicit(model) {
if (model === 'A4') return 'Audi';
return 'Mercedes-Benz';
}
function carBrandImplicit(model) {
if (model === 'A4') return 'Audi';
if (model === 'AMG') return 'Mercedes-Benz';
throw new Exception(model + ' ์ ์ฐพ์ ์ ์์ต๋๋ค.');
}
์๊ฐํด๋ณด์ง ๋ชปํ๋ ํฌ์ธํธ์๋๋ฐ, ์ข์ ๋ฐฉ๋ฒ์ธ ๊ฒ ๊ฐ๋ค์.
15์ฅ null
276p, null ๊ฐ์ฒด ์์ฑํ๊ธฐ
null ๊ฐ์ฒด ํจํด์ ์ผ๋ฐ ๊ฐ์ฒด์ฒ๋ผ ๋์ํ์ง๋ง ๊ธฐ๋ฅ์ด ๊ฑฐ์ ์๋ ํน์ ๊ฐ์ฒด์ธ 'null ๊ฐ์ฒด' ์์ฑ์ ์ ์ํฉ๋๋ค. ์ด ํจํด์ if๋ก null ์ฐธ์กฐ๋ฅผ ํ์ธํ์ง ์๊ณ ๋ null ๊ฐ์ฒด์์ ๋ฉ์๋๋ฅผ ์์ ํ๊ฒ ํธ์ถํ ์ ์๋ค๋ ์ฅ์ ์ด ์์ต๋๋ค.
public class CartItem {
private int price;
public CartItem(int price) {
this.price = price;
}
public int getPrice() {
return price;
}
}
public interface DiscountCoupon {
double discount(double subtotal);
}
public class RateDiscountCoupon implements DiscountCoupon {
private double rate;
public RateDiscountCoupon(double rate) {
this.rate = rate;
}
@Override
public double discount(double subtotal) {
return subtotal * (1 - rate);
}
}
// Null Object ํจํด
public class NullDiscountCoupon implements DiscountCoupon {
@Override
public double discount(double subtotal) {
return subtotal;
}
}
import java.util.List;
public class Cart {
private List<CartItem> items;
private DiscountCoupon discountCoupon;
public Cart(List<CartItem> items, DiscountCoupon discountCoupon) {
this.items = items;
this.discountCoupon = discountCoupon;
}
public double subtotal() {
return items.stream()
.mapToDouble(CartItem::getPrice)
.sum();
}
public double total() {
return discountCoupon.discount(subtotal());
}
}
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
Cart cartWithDiscount = new Cart(
Arrays.asList(
new CartItem(1),
new CartItem(2),
new CartItem(7)
),
new RateDiscountCoupon(0.15)
);
System.out.println("Cart total with discount: " + cartWithDiscount.total()); // 8.5
Cart cartWithoutDiscount = new Cart(
Arrays.asList(
new CartItem(1),
new CartItem(2),
new CartItem(7)
),
new NullDiscountCoupon()
);
System.out.println("Cart total without discount: " + cartWithoutDiscount.total()); // 10.0
}
}
null ๊ฐ์ฒด ํจํด์ ์ด์ ์๋ ์ ์ฉํด๋ณธ ์ผ์ด์ค๊ฐ ์์๋๋ฐ, ์ ํ์ฉํ๋ค๋ฉด ์ค์ ๋ก๋ ๊ต์ฅํ ์ ์ฉํ ํจํด์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค.
17์ฅ ๊ฒฐํฉ๋
327p, ๊ธฐ๋ฅ์ ๋ํ ์์ฌ ๋ฐฉ์งํ๊ธฐ
๋ฌธ์ : ํ ๊ฐ์ฒด๊ฐ ๋ค๋ฅธ ๊ฐ์ฒด์ ๋ฉ์๋๋ฅผ ๋๋ฌด ๋ง์ด ์ฌ์ฉํฉ๋๋ค.
ํด๊ฒฐ: ์ข ์์ฑ์ ์์ ๊ณ ๋์์ ๋ฆฌํฉํฐ๋งํ์ธ์.
๊ธฐ๋ฅ์ ๋ํ ์์ฌ์ ํ ๊ฐ์ฒด๊ฐ ๋ค๋ฅธ ๊ฐ์ฒด์ ๋ฉ์๋๋ฅผ ๊ณผ๋ํ๊ฒ ์ฌ์ฉํจ์ผ๋ก์จ ์์ ์ ๋์๋ณด๋ค ๋ค๋ฅธ ๊ฐ์ฒด์ ๋์์ ๋ ๊ด์ฌ์ ๊ฐ์ง ๋ ๋ฐ์ํฉ๋๋ค.
// Bad
public class Candidate {
void printJobAddress(Job job) {
print("...");
print(job.address().street());
print(job.address().city());
...
}
}
// Good
public class Job {
void printAddress() {
print("...");
print(address().street());
print(address().city());
...
}
}
330p, ๊ธฐ๋ณธ ์ธ์๋ฅผ ๋งจ ๋์ผ๋ก ์ด๋ํ๊ธฐ
๋ฌธ์ : ์ธ์ ๋ชฉ๋ก ์ค๊ฐ์ ๊ธฐ๋ณธ ์ธ์๊ฐ ์์ต๋๋ค.
ํด๊ฒฐ: ํจ์ ์๊ทธ๋์ณ๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ธฐ ์ฝ์ง ์์์ผ ํฉ๋๋ค. ๊ธฐ๋ณธ ์ธ์๋ฅผ ์ฌ์ฉํ์ง ์๋๋ก ํ๋, ๊ผญ ํ์ํ ๊ฒฝ์ฐ ์ ํ์ ์ธ์๋ฅผ ํ์ ์ธ์ ์์ ์ฌ์ฉํ์ง ๋ง์ธ์.
// Bad
function buildCar($color = "red", $model) {
...
}
// Good
function buildCar($model, $color = "red") {
...
}
์ฝํ๋ฆฐ๊ณผ ๊ฐ์ ์ธ์ด์์๋ ์ธ์์ ๋ณ์๋ฅผ ์ง์ ์ค์ ํด์ ๊ฐ์ ๋๊ฒจ์ค ์ ์์ง๋ง, ์์ ๊ฐ์ด ์ฌ์ฉํ๋ค๋ฉด ์ข ๋ ๊ฐ๋จํ๊ฒ ๋ํ๋ผ ์ ์์ ๊ฒ ๊ฐ๊ธด ํฉ๋๋ค.
20์ฅ ํ ์คํธ
์๋ฌด๋ฆฌ ๋ง์ ํ ์คํธ๋ฅผ ํด๋ ์ํํธ์จ์ด๊ฐ ์ ํํ๋ค๋ ๊ฒ์ ์ฆ๋ช ํ ์๋ ์์ง๋ง, ํ ๋ฒ์ ํ ์คํธ๋ก ์ํํธ์จ์ด๊ฐ ์๋ชป๋์์์ ์ฆ๋ช ํ ์๋ ์์ต๋๋ค. - ์๋ฏธ๋ฅด ๊ฐ๋ผ์ด -
384p, private ๋ฉ์๋ ํ ์คํธํ๊ธฐ
๋ฌธ์ : private ๋ฉ์๋๋ฅผ ํ ์คํธํด์ผ ํฉ๋๋ค.
ํด๊ฒฐ: private ๋ฉ์๋๋ฅผ ํ ์คํธํ์ง ๋ง๊ณ , ํด๋น ๋ฉ์๋๋ฅผ ์ถ์ถํ์ธ์
๊ฐ๋ฐ์๋ผ๋ฉด ์์ ์์ค์ ๊ธฐ๋ฅ์ ์ค์ํ๊ฒ ์ง์ํ๋ ๋ด๋ถ ํจ์๋ ๋ฉ์๋์ ๋ํ ํ ์คํธ๋ฅผ ์์ฑํด์ผ ํ๋ ์ด๋ ค์์ ์ง๋ฉดํ ์ ์ด ์์ ๊ฒ์ ๋๋ค. ๋ฉ์๋์ ์บก์ํ๋ฅผ ๊นจ๋จ๋ฆด ๊ฐ๋ฅ์ฑ์ด ์๊ณ , ๋ฉ์๋๋ฅผ ๋ณต์ฌํ๊ฑฐ๋ ๊ณต๊ฐํ๊ณ ์ถ์ง ์๊ธฐ ๋๋ฌธ์ ์ด๋ฅผ ์ง์ ํ ์คํธํ ์ ์์์ต๋๋ค.
// Bad
public final class Star {
private double distanceInParsecs;
public double timeToReachLightToUs() {
return convertDistanceInParsecsToLightYears(distanceInParsecs);
}
private double convertDistanceInParsecsToLightYears(double distanceInParsecs) {
return 3.26 * distanceInParsecs;
// ํจ์๋ ์ด๋ฏธ ์ฌ์ฉ ๊ฐ๋ฅํ ์ธ์๋ฅผ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.
// ์ด ํจ์๋ distanceInParsecs์ private ์ ๊ทผ์ ๊ฐ์ง๊ณ ์๊ธฐ ๋๋ฌธ์
// ์ด๋ ๋ ๋ค๋ฅธ ์ฝ๋ ์ค๋ฉ์ ์งํ๊ฐ ๋ฉ๋๋ค.
// ์ด ํจ์๋ private์ด๊ธฐ ๋๋ฌธ์ ํ
์คํธํ ์ ์์ต๋๋ค.
}
}
// Good
public final class Star {
private double distanceInParsecs;
public double timeToReachLightToUs() {
return new ParsecsToLightYearsConverter().convert(distanceInParsecs);
}
}
public final class ParsecsToLightYearsConverter {
public double convert(double distanceInParsecs) {
return 3.26 * distanceInParsecs;
}
}
public class ParsecsToLightYearsConverterTest {
@Test
public void testConvert0ParsecsReturns0LightYears() {
assertEquals(0, new ParsecsToLightYearsConverter().convert(0));
}
// ๋ค์ํ ํ
์คํธ๋ฅผ ์ถ๊ฐํ ์ ์์ผ๋ฉฐ, ์ด ๊ฐ์ฒด์ ์์กดํ ์ ์์ต๋๋ค.
// ๋ฐ๋ผ์ Star์ ๋ณํ์ ํ
์คํธํ ํ์๊ฐ ์์ต๋๋ค.
// ๊ทธ๋ฌ๋ ์ฌ์ ํ Star์ timeToReachLightToUs() ๋ฉ์๋๋ฅผ ํ
์คํธํ ์ ์์ต๋๋ค.
// ์ด๊ฑด ๊ฐ์ํ๋ ์๋๋ฆฌ์ค์
๋๋ค.
}
399p, ์บก์ํ๋ฅผ ์๋ฐํ๋ ํ ์คํธ ๋ณดํธํ๊ธฐ
๋ฌธ์ : ์บก์ํ๋ฅผ ์๋ฐํ๋ ํ ์คํธ๊ฐ ์์ต๋๋ค.
ํด๊ฒฐ: ํ ์คํธ๋ฅผ ์ํด ๋ฉ์๋๋ฅผ ๋ง๋ค์ง ๋ง์ธ์.
๊ฐ๋์ ํ ์คํธ๋ฅผ ์ฐ์ ์ํ๋ ์ฝ๋๋ฅผ ์์ฑํ๋ค ๋ณด๋ฉด ํด๋น ์ฝ๋๊ฐ ์บก์ํ๋ฅผ ์๋ฐํ๊ณ ๋์ ์ธํฐํ์ด์ค๋ฅผ ์ ๋ฐํด ๋ถํ์ํ ๊ฒฐํฉ์ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค. ํ ์คํธ๋ ์ ์ฒด ํ๊ฒฝ์ ํต์ ํด์ผ ํ๋ฉฐ, ์ฌ๋ฌ๋ถ์ด ๊ฐ์ฒด๋ฅผ ์ ์ดํ ์ ์๋ค๋ฉด ์์น ์๋ ๊ฒฐํฉ์ ๋ฐ๊ฒฌํ ๊ฒ์ ๋๋ค. ์ด๋ค์ ๋ถ๋ฆฌํ์ธ์.
// Bad
import java.util.Random;
public class Hangman {
private String wordToGuess;
public Hangman() {
this.wordToGuess = getRandomWord();
// ํ
์คํธ๋ ์ด๊ฒ์ ์ ์ดํ ์ ์์ต๋๋ค.
}
public String getWordToGuess() {
return wordToGuess;
// ์ํ๊น๊ฒ๋ ์ด๊ฒ์ ๊ณต๊ฐํด์ผ ํฉ๋๋ค.
}
private String getRandomWord() {
// ๋ฌด์์ ๋จ์ด ์์ฑ ๋ก์ง (๊ฐ๋จํ ์์)
String[] words = {"apple", "banana", "cherry"};
return words[new Random().nextInt(words.length)];
}
}
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
public class HangmanTest {
@Test
public void testWordIsGuessed() {
Hangman hangmanGame = new Hangman();
assertEquals("tests", hangmanGame.getWordToGuess());
// ์ด๋ป๊ฒ ๋จ์ด๊ฐ ๋ง์ถ์ด์ก๋์ง ํ์ธํ ์ ์์ต๋๊น?
}
}
// Good
public interface WordRandomizer {
String newRandomWord();
}
public class Hangman {
private String wordToGuess;
private StringBuilder guessedWord;
public Hangman(WordRandomizer wordRandomizer) {
this.wordToGuess = wordRandomizer.newRandomWord();
this.guessedWord = new StringBuilder("_".repeat(wordToGuess.length()));
}
public boolean wordWasGuessed() {
return wordToGuess.equals(guessedWord.toString());
}
public void play(char letter) {
for (int i = 0; i < wordToGuess.length(); i++) {
if (wordToGuess.charAt(i) == letter) {
guessedWord.setCharAt(i, letter);
}
}
}
}
public class MockRandomizer implements WordRandomizer {
@Override
public String newRandomWord() {
return "tests";
}
}
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
public class HangmanTest {
@Test
public void testWordIsGuessed() {
Hangman hangmanGame = new Hangman(new MockRandomizer());
// ์ด์ ์์ ํ ์ ์ด๋ฅผ ๊ฐ์ง๋๋ค!
assertFalse(hangmanGame.wordWasGuessed());
hangmanGame.play('t');
assertFalse(hangmanGame.wordWasGuessed());
hangmanGame.play('e');
assertFalse(hangmanGame.wordWasGuessed());
hangmanGame.play('s');
assertTrue(hangmanGame.wordWasGuessed());
// ์ด์ ๋์์ ํ
์คํธํฉ๋๋ค.
}
}
22์ฅ ์์ธ
420p, ์ค์ฒฉ๋ try/catch ์ฌ์์ฑํ๊ธฐ
๋ฌธ์ : ์ค์ฒฉ๋ try/catch๊ฐ ๋ง์ต๋๋ค.
ํด๊ฒฐ: ์์ธ๋ฅผ ์ค์ฒฉ์ํค์ง ๋ง์ธ์. ๊ทธ๋ ๊ฒ ๋๋ฉด ๋ด๋ถ ๋ธ๋ก์์ ์ํํ๋ ์์ ์ ๋ฐ๋ผ๊ฐ๊ธฐ ์ด๋ ต์ต๋๋ค. ์ฒ๋ฆฌ ๋ฉ์ปค๋์ฆ์ ๋ค๋ฅธ ํด๋์ค๋ ํจ์๋ก ์ถ์ถํ์ธ์.
// Bad
try {
transaction.commit();
} catch (e) {
logerror(e);
if (e instanceOf DBError) {
try {
transaction.rollback();
} catch (e) {
doMoreLoggingRollbackFailed(e);
}
}
}
// Good
try {
transaction.commit();
} catch (transactionError) {
this.withTransactionErrorDo(
transationError, transaction);
}
25์ฅ ๋ณด์
๋ณต์ก์ฑ์ ์น๋ช ์ ์ ๋๋ค. ๋ณต์ก์ฑ์ ๊ฐ๋ฐ์์ ์๊ธฐ๋ฅผ ๋นผ์์๊ฐ๊ณ ์ ํ์ ๊ณํ, ๊ตฌ์ถ, ํ ์คํธํ๊ธฐ ์ด๋ ต๊ฒ ๋ง๋ค๊ณ , ๋ณด์ ๋ฌธ์ ๋ฅผ ๋ฐ์์ํค๋ฉฐ ์ต์ข ์ฌ์ฉ์์ ๊ด๋ฆฌ์๊ฐ ์ข์ ์ ๋ง๋ณด๊ฒ ํฉ๋๋ค.
์๋์ด ๊ฐ๋ฐ์๋ ๋จ์ํ ๊นจ๋ํ๊ณ ์ ์ง ๋ณด์ ๊ฐ๋ฅํ ์ฝ๋๋ฅผ ์์ฑํ๋ ๋ฅ๋ ฅ๋ฟ๋ง ์๋๋ผ ์ฑ๋ฅ, ์์ ์ฌ์ฉ๋, ๋ณด์๊ณผ ๊ฐ์ ๋ค์ํ ์ํํธ์จ์ด ํ์ง ์์ฑ์ ๊ณ ๋ คํ ๊ฒฌ๊ณ ํ ์๋ฃจ์ ์ ๊ตฌ์ถํ๋ ๋ฅ๋ ฅ์ ๊ฐ์ ธ์ผ ํฉ๋๋ค. ์ฝ๋๋ฅผ ์์ฑํ ๋ ๋ณด์ ์ค์ฌ์ ์ธ ์ ๊ทผ์ ์ฑํํ๋ ๊ฒ์ ์ ์ฌ์ ์ธ ๋ณด์ ์ทจ์ฝ์ ์ ๋ํ ์ด๊ธฐ ๋ฐฉ์ด ์ญํ ์ ํ๊ธฐ ๋๋ฌธ์ ๋งค์ฐ ์ค์ํฉ๋๋ค.
450p, ์ ๋ ฅ๊ฐ ๊ฒ์ดํ๊ธฐ
๋ฌธ์ : ์ฌ์ฉ์ ์ ๋ ฅ์ ๊ฒ์ดํ์ง ์์ ์ฝ๋๊ฐ ์์ต๋๋ค.
ํด๊ฒฐ: ํต์ ํ ์ ์๋ ๋ชจ๋ ๊ฒ์ ๊ฒ์ดํ์ธ์.
์ค๋ช : ์ ์์ ์ธ ์ฌ์ฉ์๋ ํญ์ ์กด์ฌํฉ๋๋ค. ๋ฐ๋ผ์ ์ฌ์ฉ์์ ์ ๋ ฅ์ ๋งค์ฐ ์ฃผ์๋ฅผ ๊ธฐ์ธ์ฌ์ผ ํ๋ฉฐ, ์ ๋ ฅ ํํฐ๋ง ๊ธฐ์ ์ ์ฌ์ฉํ๊ณ ๊ฒ์ดํด์ผ ํฉ๋๋ค.
์ ๋ ฅ ๊ฒ์ด (Input Sanitization)
์ฌ์ฉ์์ ์ ๋ ฅ์ ์ฒ๋ฆฌํ๊ธฐ ์ ์ ์ ๋ ฅ์ด ์์ ํ๊ณ ํ์์ ์ค์ํ๋์ง ํ์ธํ๊ธฐ ์ํด ์ ํจ์ฑ์ ๊ฒ์ฌํ๊ณ ์ ๋ฆฌํ๋ ์์ ์ ๋๋ค. ์ ์์ ์ธ ์ฌ์ฉ์๊ฐ ์คํํ ์ ์๋ SQL ์ฝ์ , ์ฌ์ดํธ ๊ฐ ์คํฌ๋ฆฝํ (XSS), ๊ธฐํ ๊ณต๊ฒฉ๊ณผ ๊ฐ์ ๋ค์ํ ๋ณด์ ์ทจ์ฝ์ ์ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
SQL ์ฝ์ (SQL Injection)
๊ณต๊ฒฉ์๊ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํต์ ํ๋ ํ๋ก๊ทธ๋จ์ ์ ์ฑ SQL ์ฝ๋๋ฅผ ์ฝ์ ํ๋ ๊ฒ์ ๋๋ค. ๊ณต๊ฒฉ์๋ ํ ์คํธ ์์๋ ์์๊ณผ ๊ฐ์ ์ ๋ ฅ ํ๋์ SQL ์ฝ๋๋ฅผ ์ ๋ ฅํฉ๋๋ค. ๊ทธ๋ฌ๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ด ํด๋น ์ฝ๋๋ฅผ ์คํํด ๋ฐ์ดํฐ์ ์ ๊ทผํ๊ฑฐ๋ ์์ ํ๊ณ , ๋ฏผ๊ฐํ ์ ๋ณด๋ฅผ ๊ฒ์ํ๊ฑฐ๋, ์ฌ์ง์ด ์์คํ ์ ์ ์ดํ ์๋ ์์ต๋๋ค.
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class Main {
public static String sanitize(String input) {
// ๋ฌธ์์ ์ซ์๊ฐ ์๋ ๊ฒฝ์ฐ ๋ชจ๋ ์ ๊ฑฐํฉ๋๋ค.
return input.replaceAll("[^a-zA-Z0-9]", "");
}
...
}
ํ๊ธฐ
๋ชจ๋ ์ฑํฐ๊ฐ ๋ฌธ์ - ํด๊ฒฐ - ์ค๋ช - ์์ ์ฝ๋ ๋ฑ์ ๊ตฌ์กฐ๋ก ์ด๋ฃจ์ด์ ธ ์์ด์ ์ฝ๋๋ฅผ ๋ง์ด ๋ณผ ์ ์์ง๋ง, ๊ต์ฅํ ๋ค์ํ ์ธ์ด๋ค๋ก ๊ตฌ์ฑ๋์ด ์์ด์ ์คํ๋ ค ์ดํดํ๊ธฐ๊ฐ ๋ ํ๋ ๋ถ๋ถ์ด ๋ง์๋ ๊ฒ ๊ฐ์ต๋๋ค. (Java, C#, Python, Ruby, PHP, JS ...)
์์ ์ฝ๋๊ฐ ๋๋ถ๋ถ ๊ธธ์ง๋ ์์ง๋ง, ๊ฐ ์ธ์ด๋ง๋ค ์ ์ฉํ ์ ์๋ ์ผ์ด์ค๋ค๋ ์กด์ฌํด์ ์กฐ๊ธ ์์ฌ์ ๋ค์.
๋ํ ์๋ก ์์ ๋งํ ๊ฒ ์ฒ๋ผ, ํด๋ฆฐ ์ฝ๋์ ์ค๋ณต ๋ด์ฉ๋ค์ด ๊ต์ฅํ ๋ง์ด ์กด์ฌํ๋ ๊ฒ ๊ฐ์ ๋๋์ด์์ต๋๋ค.
"ํ๋น๋ฏธ๋์ด <๋๋ ๋ฆฌ๋ทฐ์ด๋ค> ํ๋์ ์ํด์ ์ฑ ์ ์ ๊ณต๋ฐ์ ์์ฑ๋ ์ํ์ ๋๋ค."
'๋์ ๋ฆฌ๋ทฐ > IT' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
์ผํธ ๋ฐฑ์ Tidy First? ๋์ ๋ฆฌ๋ทฐ (2) | 2024.05.25 |
---|---|
์๋ฐ์ JUnit์ ํ์ฉํ ์ค์ฉ์ฃผ์ ๋จ์ ํ ์คํธ ๋ฆฌ๋ทฐ (0) | 2022.04.16 |
[IT] ๊ฐ์ฒด์งํฅ์ ์ฌ์ค๊ณผ ์คํด (0) | 2021.01.31 |
๋๊ธ