어떤 프로그램이든 가장 기본단위가 함수이기에 함수를 잘 만드는 법이 중요하다.
모니터와 ide의 발달로 한 화면에 세로 100줄도 들어가지만 우리는 함수를 더 작게, 짧게 유지해야만 한다.
if-else 문, while 문에 들어가는 블록은 한줄이여야 한다.
즉, 중첩구조는 함수가 커지는 지름길이다. 함수의 들여쓰기 수준은 1단이나 2단을 넘어서면 안된다.
제곧네. 단순히 다른 표현이 아니라, 의미 있는 이름으로 함수 안에 다른 함수를 추출할 수 있다면 추출하여 함수를 한가지 일을 하도록 만들것.
함수가 한가지 작업만 수행하려면 함수 내 모든 문장의 추상화 수준이 동일해야함.
추상화 수준이 같다는게 뭐지?
- "차를 운전한다"라는 함수의 문장들 ("열쇠를 찾는다", "시동을 건다", "기어를 바꾼다", "방향을 조절한다")은 같은 추상화 수준에서 작동한다. 이들은 모두 "차를 운전한다"라는 한 가지 일을 수행하기 위한 구체적인 단계들이다.
- "열쇠를 찾는다"라는 함수의 문장들 ("가방을 뒤진다", "키 홀더를 확인한다")은 더 낮은 수준의 추상화를 나타낸다. 이들은 "열쇠를 찾는다"라는 한 가지 작업을 수행하기 위한 구체적인 단계들이다.
- 함수의 추상화 수준을 판단하는 것은 그 함수 내의 문장들이 어떤 수준의 작업을 나타내는지 확인하는 것이다.
- 함수 내의 모든 문장이 같은 수준의 작업을 나타낸다면, 그 함수는 한 가지 일을 하는 것으로 본다.
위에서 아래로 프로그램을 읽을 때 추상화 수준이 한번에 한단계씩 낮아지도록 할 것.
class Animal {
String type;
void act() {
switch (type) {
case "Dog":
// Dog의 행동을 정의
break;
case "Cat":
// Cat의 행동을 정의
break;
// ... 추가적인 동물 종류
default:
// 기본 행동
break;
}
}
}
이런 방식으로 코드를 작성하면, 새로운 동물 종류가 추가될 때마다 switch 문을 수정해야하고, 동일한 행동 코드가 여러 곳에서 반복될 수 있음.다형성을 이용한 switch 문 숨기기
다형성을 이용하면 switch 문을 저차원 클래스에 숨길 수 있음.
이 방법은 코드 반복을 방지하고, switch 문이 한 가지 작업만 하게 만들 수 있음.
예를 들면, 동물의 종류에 따라 다른 행동을 하는 경우를 생각해보자.
동물 클래스를 만들고, 각 동물 종류를 나타내는 서브클래스를 만들 수 있다. 각 서브클래스는 동물 클래스의 '행동' 메소드를 오버라이드하여 해당 종류의 동물이 특정 행동을 어떻게 하는지 정의할 수 있다.
```java
abstract class Animal {
abstract void act();
}
class Dog extends Animal {
void act() {
// Dog의 행동을 정의
}
}
class Cat extends Animal {
void act() {
// Cat의 행동을 정의
}
}
```
이렇게 하면, Animal 타입의 객체를 사용할 때는 각 동물의 특정 행동을 알 필요 없이 'act' 메소드를 호출하면 된다.
```java
Animal myPet = new Dog();
myPet.act(); // Dog의 행동을 실행
```
이런 방식으로 switch 문을 다형성을 이용해 숨기면, 각 동물 종류에 따른 행동 코드를 한 곳에서 관리할 수 있고, 코드의 반복을 줄일 수 있다.
💡 해당 부분은 필자의 지식 부족으로 gpt의 도움을 받았습니다 🥲
java코드 복사
public interface Animal {
void act();
}
java코드 복사
public class Dog implements Animal {
@Override
public void act() {
System.out.println("Dog is acting.");
}
}
public class Cat implements Animal {
@Override
public void act() {
System.out.println("Cat is acting.");
}
}
java코드 복사
public interface AnimalFactory {
Animal createAnimal();
}
java코드 복사
public class DogFactory implements AnimalFactory {
@Override
public Animal createAnimal() {
return new Dog();
}
}
public class CatFactory implements AnimalFactory {
@Override
public Animal createAnimal() {
return new Cat();
}
}
java코드 복사
public class Client {
public static void main(String[] args) {
AnimalFactory dogFactory = new DogFactory();
Animal dog = dogFactory.createAnimal();
dog.act(); // Output: Dog is acting.
AnimalFactory catFactory = new CatFactory();
Animal cat = catFactory.createAnimal();
cat.act(); // Output: Cat is acting.
}
}
java코드 복사
public class FactoryProvider {
public static AnimalFactory getFactory(String type) {
switch (type) {
case "Dog":
return new DogFactory();
case "Cat":
return new CatFactory();
default:
throw new IllegalArgumentException("Unknown animal type: " + type);
}
}
}
java코드 복사
public class Client {
public static void main(String[] args) {
AnimalFactory factory = FactoryProvider.getFactory("Dog");
Animal animal = factory.createAnimal();
animal.act(); // Output: Dog is acting.
factory = FactoryProvider.getFactory("Cat");
animal = factory.createAnimal();
animal.act(); // Output: Cat is acting.
}
}
이 예제에서는 FactoryProvider
클래스의 getFactory
메서드가 switch
문을 사용하여 적절한 팩토리를 반환합니다. 하지만 클라이언트 코드에서는 switch
문을 보지 않고, 추상 팩토리 인터페이스를 통해 다형성을 활용하여 동물을 생성하고 행동을 수행합니다. 이를 통해 switch
문을 효과적으로 숨기고, 추상 팩토리 패턴과 다형성을 결합하여 코드의 유연성과 유지보수성을 높일 수 있습니다. switch 문 숨기기출력인수란?
출력 인수는 함수가 전달받은 값을 바꿔서 그 바뀐 값을 함수 밖에서도 사용할 수 있게 하는 것
public void 추가하기(List<String> list, String data) { list.add(data); // 전달받은 list에 data를 추가합니다. } ... 추가하기(myList, "안녕");
여기서
list
가 출력 인수이다.→ 함수에서 상태를 변경해야 한다면 함수가 속한 객체 상태를 변경하는 방식을 택할 것
ex)
appendFooter(StringBuffer report)
→report.appendFooter()
인수에 질문을 던지는 경우
ex) boolean fileExists(”Myfile”);
인수로 뭔가를 변환해 결과를 반환하는 경우.
ex) InputStream fileOpen(”MyFile”)
: String의 파일 이름을 InputStream으로 변환하는 함수
→ 단, 함수의 이름을 지을 때는 두 경우를 분명히 구문하게 하여 짓는다.
이벤트 함수
→ 단, 이벤트라는 사실이 함수 이름에 명확히 드러나야 함.
ex) passwordAttempFailedNtimes(int attempts)
동사(명사)
형식으로 쌍을 이룰 것. ex) write(name)
단 가능한 경우가 몇가지 있음
Point p = new Point(0,0)
: 직교 좌표계의 점은 한값을 표현하는 두 요소가 필요.assertEquals(expected, actual)
: 해당 함수는 두 인수의 순서가 인위적이기 때문에 assertExpectedEqualsActual(expected, actual)
로 인수의 순서를 알리기.Point p = new Point(0,0)
의 경우 3차원일 경우 3개 4차원일 경우 4개 이상의 인수가 필요해진다. 그럴 경우에는 vector 클래스를 따로 생성해서 관리하는 것이 나을 수 있음.이런 짓은 시간적인 결합
, 순서 종속성
을 초래함.
뭔가를 수행하거나 뭔가에 답하거나 둘 중 하나만 할 것.
if (deletePage(page) = E_OK) {
if (registry.deleteReference(page.name) = E_OK)
if (configkeys.deleteKey(page.name.makeKey()) logger.log("page deleted");
}
else {
logger.log("configkey not deleted");
} {
= =
} else {
logger.log("deleteReference from registry failed");
}
} else {
logger.log("delete failed");
return E_ERROR;
}
try-catch 문으로 오류 처리를 원래 코드에서 분리하면 코드를 깔끔하게 할 수 있음.
private void deletePageAndAllReferences(Page page) throws Exception {
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
}
private void logError(Exception e) {
logger.log(e.getMessage());
}
또한 try catch
구문을 별도 함수로 뽑아낸다면 더욱 좋음 → 오류처리도 한가지 일이기에 오류 처리를 별도로 처리하는 함수를 오류만 처리하게 나눠야함.