클린코드 7장(경계), 8장 (단위 테스트)

JAVA/클린코드|2020. 7. 1. 09:18

 

 

경계


  • Map과 같은 공개된 인터페이스를 사용할 때 2가지 문제가 있다
    1. 전달되는 map에 clear와 같은 삭제 명령어가 있어서 전달해주는 쪽에서 알지 못하고 지워지는 이슈가 있을 수 있다.

    2. 전달된 Map 형태가 generic 하게 전해 졌어도 받는 쪽에서 Map map 등으로 받아 버린다면 캐스팅을 해서 사용하는 등의 문제나 변경의 소지가 크다. 또한 Map의 사용 방식이 변경된다면 이를 해결하기 위해 모든 부분에서 수정되어야 하는 문제가 있다.

    그래서 이를 해결하기 위해서 Map에 사용 되는 Value 값의 객체에서 Map을 품어서 쓰는 방식으로 캡슐화하여 사용하기도 한다.
public classs Sensors {
	private Map<String, Sensors> = new HashMap<>();

	public Sensor getById(String id) {
		return (Sensor) sensors.get(id);
	}

}
  • 외부 라이브러리를 사용할 경우 시간 단축에 효과가 있지만 잘못 사용함에 있어서 발생하는 영향도가 얼마나 있을지 알지 못하기 때문에 학습 테스트를 통해 외부 api에 대한 테스트를 미리 진행한다.
  • 외부 라이브러리에 대한 학습 테스트를 진행하여 사용하면 해당 라이브러리의 버전이 올라갔을 때 우리가 사용하는 목적에 맞게 정상적으로 돌아가는지 쉽게 테스트를 해볼 수 있어서 좋다.
  • Adapter pattern을 이용하여 변경의 요지가 있는 인터페이스를 방어할 수 있다.
public interface Bark {
	void speak();
}

// dog는 짖으므로 Bark 인터페이스 사용가능
public Dog implements Bark {
	@Override
	public void speak() {
		..
	}
}

// 고양이 야용 인터페이스
public interface Meow {
	void speak();
}

// 고양이는 짖기 어려움
public Cat implements Meow {
	@Override
	public void speak() {
	
	}
}

// 고양이가 짖을 수 있는 adpater 생성
public BarkCat implements Bark {
	private Meow meow;

	public BarkCat(Meow meow) {
		this.meow = meow;
	}
	
	@Override
	public void speak() {
		meow.speak();
	}
	
}

 

 

 

단위 테스트


  • 단위 테스트
    1. 실패하는 단위 테스트를 작성할 때 까지 실제 코드를 작성하지 않는다.
    2. 컴파일이 실패하지 않으면서 실행이 실패하는 정도로만 단위테스트를 작성한다.
    3. 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다.

    하지만 이렇게 모두 작성하면 시간이 너무 걸려서 사실 어렵다.
  • 실제 코드가 변경될 때 마다 테스트 코드는 계속 변경의 여지가 있기에 지저분한 테스트 코드를 짜느니 없으니만 못하다. 그렇기에 실제 코드 못지 않게 중요하게 테스트 코드를 작성하라.
  • 테스트 코드 없는 실제 코드 변경은 잠정적인 버그와 같다.
  • 깨끗하고 효율성있는 테스트 코드를 작성하기 위해서는 무엇보다 중요한 건 가독성이다.
  • BUILD-OPERATE-CHECK 패턴을 사용하여 테스트 구조를 간결하게 작성한다.
  • 테스트 코드의 가독성을 위해서 실제 코드에서 지켜야 할 모든 규칙을 지킬 필요는 없다.
  • 하나의 테스트당 assert문이 많으면 좋지 않지만 그렇다고 테스트당 하나의 assert문을 사용하는건 중복코드를 양성할 수 있다. (적절하게 사용)
  • 하나의 테스트당 assert문이 많으면 좋지 않지만 그렇다고 테스트당 하나의 assert문을 사용하는건 중복코드를 양성할 수 있다. (적절하게 사용)
  • 깨끗한 테스트 코드의 FIRST 규칙
    F : 테스트는 빨라야 한다.
    I : 테스트는 서로 의존적이면 안된다.
    R : 테스트는 어떤 환경에서도 반복 가능해야 한다.
    S : 테스트는 통과 실패 두가지 검증 결과를 내보내야 한다.
    T : 단위테스트는 적시에 테스트 코드가 존재해야한다. (실제 구현 코드를 작성하기 이전에 작성되어야 한다.)

 

 

 

결론


개발을 하다보면 일정에 쫓기거나 테스트 코드 작성이 번거로워서 넘어가는 경우가 있는데 무조건 작성할 수 있도록 해야겠다. 그리고 테스트를 내가 알아볼 수 있도록 기능 동작에 중점을 주어서 했었는데 그게 결국 가독성과 재사용성을 떨어트려서 팀의 개발 속도를 저해 할수도 있다는 걸 알게 되어 조심해야겠다.

 

댓글()

리액티브 스트림의 이해

web/마이크로서비스|2018. 10. 4. 23:47

리액티브 스트림은 총 4개의 인터페이스로 구성되어 있다. 

ㅁ 발행자(Publisher) 

- 데이터의 소스를 가지고 있으며 Subscriber의 요청이 오면 데이터를 발행한다. 구독자는 발행자에 대한 구독을 추가할 수 있다. Subscribe 메소드를 통해서 구독자를 추가할수 있다.

1
2
3
public interface Publisher<T> {
    public void subscribe(Subscriber<? Super T> s);
}
cs


ㅁ 구독자 (Subscriber) 

-  구독자는 데이터 스트림을 소비하기 위해 발행자를 구독한다. 구독자는 다양한 메서드를 제공하는데 대부분의 메서드가 콜백으로 등록되어 사용된다.

1
2
3
4
5
6
public interface Subscriber<T> {
  public void onSubscribe(Subscription s);
  public void onNext(T t);
  public void onError(Throwable t);
  public void onComplete();
}
cs


ㅁ 구독 (subscription) 

단 하나의 발행자와 단 하나의 구독자를 연결해주며 그 둘 사이에서만 데이터 교환을 중재한다. 데이터 교환은 구독자의 request 메서드 호출로 실행되고 cancel로 종료된다.

1
2
3
4
public interface Subscription {
  public void request(long n);
  public void cancel();  
cs


ㅁ 프로세서 (procesor) 

프로세서는 처리 단계를 나타내며, 발행자 인터페이스와 구독자 인터페이스를 모두 상속한다. 프로세서는 발행자와 구독자 사이의 계약을 반드시 준수해야한다. 프로세서는 발행자와 구독자를 연결해서 chaining(메서드가 반환하는 객체를 다른 변수에 할당하지 않고 객체가 가지고 있는 메소드를 호출하는 것)을 할 수도 있다.

1
2
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}
cs



리액터에는 발행자 플로세서로 사용되는 flux와 mono가 있다. flux는 0 또는 N개의 이벤트를 발행할 수 있고, Mono는 0 또는 1 개의 이벤트만 발행할 수 있다. 그래서 다수의 데이터 요소 또는 값의 리스트를 스트림으로 전송할 때만 사용된다.

댓글()

규칙 62 - 메서드에서 던져지는 모든 예외에 대해 문서를 남겨라.

JAVA/Effective Java|2018. 6. 1. 23:17

메서드를 올바르게 사용하려면 메서드에서 던져지는 예외에 대한 설명이 문서에 있어야 한다.


그리고 


메서드가 던질수있는 모든 무점검 예외까지 선언할 필요는 없지만 점검지점 예외들과 마찬가지로주의해서 문서로 남겨놓으면 좋다.


특히 Javadoc @throws 태그를 사용해서 메서드에서 발생 가능한 모든 무점검 예외에 대한 문서를 남겨야 한다. 하지만 메서드 선언부의 throws 뒤에 무점검 예외를 나열하지는 말아야 한다.





요약하자면 메서드가 던질 가능성이 있는 모든 예외를 문서로 남겨라. 

점검지점 예외, 무점검 예외도 남겨라.



이를 지키지 않으면 해당 API를 사용하는 다른사람들이 효과적으로 사용하는게 어려워진다.




출처 : 조슈아 블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1), 규칙62 인용

댓글()

Java8 함수형 인터페이스 만들어서 사용하기

JAVA/Java 8|2018. 5. 31. 07:46

Java8 함수형 인터페이스 만들어서 사용하기

함수형 인터페이스 사용 
-> 정의한 함수형 인터페이스를 람다식을 이용하여 사용할 수 있다.




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//     함수형 인터페이스 선언 
//  함수형 인터페이스를 만들고자 할 경우에는 @FunctionalInterface 애노테이션을 붙혀야 한다.
 
@FunctionalInterface
public interface WedulInterface {
    public void print(int x);
}
 
public static void main(String args[]) {
    WedulInterface wedul = new WedulInterface() {
        @Override
        public void print(int x) {
            System.out.println(x);
        }
    };
        
    List<Integer> a = Arrays.asList(1,23);
    a.stream().forEach(wedul::print);
}
cs





댓글()

자바 8에서 java.util.function 패키지에 추가된 기본 함수형 인터페이스 정리

JAVA/Java 8|2018. 5. 31. 07:45

자바 8에서 java.util.function 패키지에 추가된 기본 함수형 인터페이스 정리

Function <T, R> 
=> T 입력으로 R 출력하여 반환



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Java8Test {
 
    public static void main(String args[]) {
        
        Function<String, Integer> mapStrToInt = new Function<String, Integer>() {
            public Integer apply(String str) {
                if (str == "wedul") {
                    return 1;
                }
                return 2;
            }
        };
        
        List<String> testData = Arrays.asList("wedul","dd","babo");
        testData.stream().map(mapStrToInt).forEach(System.out::println);
    }
}
cs




Predicate<T> 
-> T 입력으로 boolean 출력으로 반환


1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String args[]) {
        
    Predicate<Integer> predictData = new Predicate<Integer> () {
        @Override
        public boolean test(Integer tes) {
            return tes > 10
        }
    };
        
    List<Integer> test = Arrays.asList(12,3,4,55);
        
    test.stream().filter(predictData).forEach(System.out::println);
}
cs



Consumer <T>  
-> T 입력으로 아무것도 반환하지 않는다
-> 단순하게 입력받은 데이터를 처리할때 사용된다.



1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String args[]) {
        
    Consumer<Integer> consumerData  = new Consumer<Integer>() {
        @Override
        public void accept(Integer idnt) {
            System.out.println(idnt);
        }
    };
        
    List<Integer> test = Arrays.asList(12,3,4,55);
        
    test.stream().forEach(consumerData);
}
cs




Supplier<T> : 입력을 취하지 않고 T 반환
입력한 데이터를 가지고 있으며get 메소드를 사용하여 호출한 곳에서 입력된 데이터를 사용할  있게 반환  주는 기능 제공



1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String args[]) {
        
    Supplier<Integer> supplier = new Supplier<Integer>() {
        @Override 
        public Integer get() {
            return 1;
        }
    };
        
//        List<Integer> test = Arrays.asList(12,3,4,55);
    System.out.println(supplier.get());
}
cs


댓글()

Java8 인터페이스의 정적 메소드

JAVA/Java 8|2018. 5. 30. 07:42

인터페이스의 정적 메소드
 
 Java 8 부터는 인터페이스에 정적 메소드를 추가할  있다.
원래부터 인터페이스에 정적 메소드를 추가하면 안되는 이유는 없었으나단지 추상 명세로서 변하면 안된다는 인터페이스의 정신에 어긋 나는 것으로 여겨져 금지되어 왔다.




지금 까지는 일반적으로 인터페이스와 동반하는 클래스들에 정적 메소드를 두었다.

하지만 이제는 사용이 가능하다.





'JAVA > Java 8' 카테고리의 다른 글

Java8 스트림(stream) 연산  (0) 2018.05.31
Java8 스트림(Stream) API  (0) 2018.05.31
Java8 인터페이스의 정적 메소드  (0) 2018.05.30
Java8 인터페이스 default Method (디폴트 메소드)  (0) 2018.05.30
Java8 변수 유효 범위  (0) 2018.05.30
Java8 생성자 레퍼런스  (0) 2018.05.30

댓글()

Java8 기초 설명

JAVA/Java 8|2018. 5. 30. 07:29

Java8과 Java7은 많은 부분이 바뀌어서 이제

Java8을 모르는 사람은 다른사람들과 협업하기 어려워 질 수도 있다.
작년에 공부하였던 부분을 다시 복습 할겸 포스팅을 진행한다.

Java8이 도입되면서 많이 변경된 부분이 함수형 프로그램이 가능하도록 되었다는 것이다.
그것의 중심에는 람다식 표현식이 있다.

람다 표현식(lambda expression)
 람다식 이란? 
단순히 정의하면프로그래밍에서 식별값 없이 실행   있는 함수 표현 방법함수를 변수에 할당파라미터로 전달하는게 가능
이미 많은 언어에서 지원하고 있다. Ruby, C#, Python,,,,,
그러나 자바는 함수형 프로그램 언어가 아니 여서 기존에 사용할  없었다.
 
 함수형 인터페이스(functional interface)
 함수 인터페이스functional interface는 단 하나의 추상 메소드가 정의 가능한 인터페이스이다. 인터페이스가 함수형 인터페이스임을 나타내는 수단으로서 FunctionalInterface 어노테이션이 도입되었다. 예를 들어, java.lang.Runnable은 다음과 같은 함수 인터페이스를 지닌다.
    @FunctionalInterface
    public interface Runnable {
        public abstract void run();
    }
하지만, 어노테이션을 통해 명시적으로 지정하지 않더라도 함수 인터페이스의 정의를 만족하는 인터페이스라면 자바 컴파일러가 주석의 유무에 상관없이 함수 인터페이스로서 취급한다
 
 람다식 기본 구성
 
  (int x, int y) -> { return x + y; }
  (x, y) -> x + y
  x -> x * x
  () -> x
  x -> { System.out.println(x); }
 
화살표(->) 왼쪽은 매개변수 목록  입력 값 유형이고오른쪽은 몸체  동작에 대한 정의이다.




1
2
3
4
5
6
7
8
9
10
// 람다 표현식 기본 예제
public class Java8Test {
    public static void main(String args[]) {
        Thread testThread = new Thread(() -> { 
            System.out.println("best wedul");
        });
        
        testThread.start();
    }
}
cs





※ 이미지 참조 : slideshare



 매개변수 타입 추론



매개변수 타입 추론 1.
numbers.forEach((Integer value) -> System.out.println(value));

-- forEach 메소드와 Consumer 인터페이스 
numbers.forEach(new Consumer<Integer>() {
    public void accept(Integer value) {
        System.out.println(value);
    }
});

 위의 예제의 경우 자바 컴파일러가 람다식을 분석해 Consumer 인터페이스의  구현된 메소드(accept) 동일한 타입(시그니처)인지 검사하고람다식을 마치 Consumer 인터페이스를 구현한 클래스의 인스턴스인  처럼 사용한다.
 
 또한 람다 식의 인수의 타입 선언은  부분의 경우 컴파일러가 추론할  있고다음과 같이 생략이 가능하다.
numbers.forEach(value -> System.out.println(value)); 





매개변수 타입 추론 2.


1
2
3
4
5
6
7
8
9
b.addActionListener((ActionEvent e -> {
   Text.setText("버튼 클릭");
});
 
Public void addActionListener(ActionListener l) {
   Public interface ActionListener{
     Void actionPerforemd(ActionEvent e)
  }
}
cs



자바 컴파일러가 람다식을 분석해 ActionListener 인터페이스의  구현된 메소드(actionPerformed) 동일한 타입인지 검사한다.


 람다식 장점 
 
코드의 간결성 : 효율적인 람다 함수의 사용을 통하여 불필요한 코드를 삭제할  있다.
 - 필요한 정보만을 사용하는 방식을 통한 퍼포먼스 향상 : 지연 연산을 지원하는 방식을 통하여 효율적인 퍼포먼스를 기대


'JAVA > Java 8' 카테고리의 다른 글

Java8 인터페이스의 정적 메소드  (0) 2018.05.30
Java8 인터페이스 default Method (디폴트 메소드)  (0) 2018.05.30
Java8 변수 유효 범위  (0) 2018.05.30
Java8 생성자 레퍼런스  (0) 2018.05.30
Java8 메서드 레퍼런스  (0) 2018.05.30
Java8 기초 설명  (0) 2018.05.30

댓글()

클래스와 인터페이스 - 규칙22 멤버 클래스는 가능하면 static으로 선언하라.

JAVA/Effective Java|2018. 5. 29. 22:43

중첩 클래스 (nested class)는 다른 클래스안에 선언된 클래스이다. 

이런 중첩 클래스는 4가지 종류로 구성되어있다.
1. static member class
2. non-static member class
3. anonymous class
4. local class.

2 ~ 4번 클래스는 내부 클래스(inner class)이다.


상황별 중첩 클래스 사용

그럼 위에 출력한 4가지의 중첩 클래스의 적절한 사용 시기를 알아보자.

1. 정적 멤버 클래스 (static member class)
- 정적 멤버 클래스는 바깥 클래스의 모든 멤버에 접근할 수 있다. (private 멤버 변수도 접근 가능)
- 다른 정적 멤버와 동일하게 접근 권한 규칙을 따른다.
- private static member class로 선언할 시 해당 클래스에 접근 가능한 클래스는 바깥 클래스이다.

2. 비 정적 멤버 클래스 (nonstatic member class)
- 비 정적 클래스 객체는 바깥 클래스 객체와 바로 연동이된다.
=> 바깥 클래스의 메소드도 호출 할 수 있으며, this 키워드를 통해 바깥 객체에 대한 참조도 얻을 수 있다. (this  키워드를 사용하여 바깥 클래스를 접근 하기 위해서는 바깥 클래스 이름을 기재하여 사용해야 한다. ClassName.this )
- 정적 클래스와 달리 단독으로 존재 할 수 없다.


위와 같은 이유로
비정적 클래스를 사용할 경우에는 바깥 클래스의 멤버 요소들을 사용하지 않아도 
접근이 가능하기 때문에 시간과 공간 요구량이 늘어난다. ( 이는 garbage collection이 쓰레기 처리하는데 어렵게한다.)

그러므로
바깥 클래스의 내부요소에 접근하지 않을 경우에는 
무조건 정적 클래스로 선언해야 한다.


3. 익명 클래스 (anonymous class)
- 익명 클래스는 사용하는 순간에 선언하고 객체를 만든다.
- 표현식 문법을 준수



댓글()