JavaRush /Java Blog /Random-KO /커피 브레이크 #85. 제가 힘들게 배운 세 가지 Java 교훈입니다. 코드에서 SOLID 원칙을 사용...

커피 브레이크 #85. 제가 힘들게 배운 세 가지 Java 교훈입니다. 코드에서 SOLID 원칙을 사용하는 방법

Random-KO 그룹에 게시되었습니다

내가 힘들게 배운 세 가지 Java 교훈

출처: 중간 학습 Java는 어렵습니다. 나는 내 실수로부터 배웠다. 이제 당신도 꼭 가질 필요는 없는 나의 실수와 쓰라린 경험으로부터 배울 수 있습니다. 커피 브레이크 #85.  제가 힘들게 배운 세 가지 Java 교훈입니다.  코드에서 SOLID 원칙을 사용하는 방법 - 1

1. 람다는 문제를 일으킬 수 있습니다.

람다는 코드 4줄을 초과하는 경우가 많으며 예상보다 큽니다. 이는 작업 기억에 부담을 줍니다. 람다에서 변수를 변경해야 합니까? 당신은 그렇게 할 수 없습니다. 왜? 람다가 호출 사이트 변수에 액세스할 수 있으면 스레딩 문제가 발생할 수 있습니다. 따라서 람다에서 변수를 변경할 수 없습니다. 그러나 람다의 Happy 경로는 잘 작동합니다. 런타임 실패 후 다음 응답을 받게 됩니다.
at [CLASS].lambda$null$2([CLASS].java:85)
at [CLASS]$$Lambda$64/730559617.accept(Unknown Source)
람다 스택 추적을 따라가는 것은 어렵습니다. 이름은 혼란스럽고 추적 및 디버깅이 어렵습니다. 더 많은 람다 = 더 많은 스택 추적. 람다를 디버깅하는 가장 좋은 방법은 무엇입니까? 중간 결과를 사용하십시오.
map(elem -> {
 int result = elem.getResult();
 return result;
});
또 다른 좋은 방법은 고급 IntelliJ 디버깅 기술을 사용하는 것입니다. TAB을 사용하여 디버깅하려는 코드를 선택하고 이를 중간 결과와 결합합니다. “람다가 포함된 줄에서 멈출 때 F7(한 단계씩 실행)을 누르면 IntelliJ가 디버깅해야 하는 조각을 강조 표시합니다. Tab을 사용하여 블록을 디버그로 전환할 수 있으며 결정한 후에 F7을 다시 누르십시오.” 람다에서 호출 사이트 변수에 액세스하는 방법은 무엇입니까? 최종 또는 실제 최종 변수에만 액세스할 수 있습니다. 통화 위치 변수 주위에 래퍼를 만들어야 합니다. AtomicType을 사용하거나 자신의 유형을 사용합니다. 생성된 변수 래퍼를 람다로 변경할 수 있습니다. 스택 추적 문제를 해결하는 방법은 무엇입니까? 명명된 함수를 사용합니다. 이렇게 하면 담당 코드를 빠르게 찾고, 논리를 확인하고, 문제를 해결할 수 있습니다. 명명된 함수를 사용하여 암호화된 스택 추적을 제거합니다. 동일한 람다가 반복됩니까? 이름이 지정된 함수에 배치하세요. 단일 참조 지점을 갖게 됩니다. 각 람다는 생성된 함수를 가져오므로 추적하기가 어렵습니다.
lambda$yourNamedFunction
lambda$0
명명된 함수는 다른 문제를 해결합니다. 큰 람다. 명명된 함수는 큰 람다를 분할하고, 더 작은 코드 조각을 만들고, 플러그 가능한 함수를 만듭니다.
.map(this::namedFunc1).filter(this::namedFilter1).map(this::namedFunc2)

2. 목록 관련 문제

목록( Lists ) 작업을 해야 합니다 . 데이터에는 HashMap이 필요합니다 . 역할의 경우 TreeMap 이 필요합니다 . 목록은 계속됩니다. 그리고 컬렉션 작업을 피할 수 있는 방법은 없습니다. 목록을 만드는 방법은 무엇입니까? 어떤 종류의 목록이 필요합니까? 불변이어야 할까요, 아니면 가변이어야 할까요? 이러한 모든 답변은 코드의 미래에 영향을 미칩니다. 나중에 후회하지 않도록 올바른 목록을 미리 선택하세요. Arrays::asList는 "end-to-end" 목록을 생성합니다. 이 목록으로 무엇을 할 수 없나요? 크기를 조정할 수 없습니다. 그는 변하지 않습니다. 여기서 무엇을 할 수 있나요? 크기에 영향을 주지 않는 요소, 정렬 또는 기타 작업을 지정합니다. Arrays::asList 의 크기는 변경할 수 없지만 내용은 변경할 수 없으므로 주의해서 사용하세요 . new ArrayList()는 새로운 "변경 가능한" 목록을 생성합니다. 생성된 목록은 어떤 작업을 지원하나요? 이것이 바로 조심해야 할 이유입니다. new ArrayList() 를 사용하여 변경 불가능한 목록에서 변경 가능한 목록을 만듭니다 . List::of는 "불변" 컬렉션을 생성합니다. 특정 조건에서는 크기와 내용이 변경되지 않습니다. 콘텐츠가 int 와 같은 기본 데이터인 경우 목록은 변경할 수 없습니다. 다음 예를 살펴보십시오.
@Test
public void testListOfBuilders() {
  System.out.println("### TESTING listOF with mutable content ###");

  StringBuilder one = new StringBuilder();
  one.append("a");

  StringBuilder two = new StringBuilder();
  two.append("a");

  List<StringBuilder> asList = List.of(one, two);

  asList.get(0).append("123");

  System.out.println(asList.get(0).toString());
}
### 변경 가능한 콘텐츠가 포함된 테스트 목록 ### a123
불변 객체를 생성하여 List::of 에 삽입해야 합니다 . 그러나 List::of는 불변성을 보장하지 않습니다. List::of는 불변성, 신뢰성 및 가독성을 제공합니다. 언제 가변 구조를 사용해야 하는지, 언제 불변 구조를 사용해야 하는지 알아보세요. 변경해서는 안되는 인수 목록은 변경할 수 없는 목록에 있어야 합니다. 변경 가능한 목록은 변경 가능한 목록일 수 있습니다. 안정적인 코드를 생성하려면 어떤 컬렉션이 필요한지 이해하세요.

3. 주석으로 인해 속도가 느려집니다.

주석을 사용하시나요? 당신은 그들을 이해합니까? 그들이 무엇을 하는지 아시나요? Logged 주석이 모든 방법에 적합하다고 생각한다면 틀린 것입니다. Logged를 사용하여 메서드 인수를 기록했습니다. 놀랍게도 작동하지 않았습니다.
@Transaction
@Method("GET")
@PathElement("time")
@PathElement("date")
@Autowired
@Secure("ROLE_ADMIN")
public void manage(@Qualifier('time')int time) {
...
}
이 코드에 어떤 문제가 있나요? 여기에는 많은 구성 다이제스트가 있습니다. 당신은 이런 일을 여러 번 겪게 될 것입니다. 구성은 일반 코드와 혼합되어 있습니다. 그 자체로는 나쁘지 않지만 눈을 사로잡습니다. 상용구 코드를 줄이려면 주석이 필요합니다. 각 엔드포인트에 대한 로깅 논리를 작성할 필요가 없습니다. 트랜잭션을 설정할 필요가 없습니다. @Transactional을 사용하세요 . 주석은 코드를 추출하여 패턴을 줄입니다. 둘 다 게임에 참여하고 있기 때문에 여기에는 확실한 승자가 없습니다. 저는 아직도 XML과 주석을 사용합니다. 반복되는 패턴을 찾으면 논리를 주석으로 옮기는 것이 가장 좋습니다. 예를 들어 로깅은 좋은 주석 옵션입니다. 교훈: 주석을 과도하게 사용하지 말고 XML을 잊지 마세요.

보너스: Optional에 문제가 있을 수 있습니다.

Optional 에서 orElse 를 사용합니다 . orElse 상수를 전달하지 않으면 바람직하지 않은 동작이 발생합니다 . 향후 문제를 예방하려면 이 점을 숙지해야 합니다. 몇 가지 예를 살펴보겠습니다. getValue(x)가 값을 반환 하면 getValue(y)가 실행됩니다 . getValue(x)가 비어 있지 않은 Optional 값을 반환하면 orElse 의 메서드가 실행됩니다 .
getValue(x).orElse(getValue(y)
                  .orElseThrow(() -> new NotFoundException("value not present")));

public Optional<Value> getValue(Source s)
{
  System.out.println("Source: " + s.getName());

  // returns value from s source
}

// when getValue(x) is present system will output
Source: x
Source: y
orElseGet 을 사용하세요 . 비어 있지 않은 Optionals 에 대한 코드는 실행되지 않습니다 .
getValue(x).orElseGet(() -> getValue(y)
                  .orElseThrow(() -> new NotFoundException("value not present")));

public Optional<Value> getValue(Source s)
{
  System.out.println("Source: " + s.getName());

  // returns value from s source
}

// when getValue(x) is present system will output
Source: x

결론

자바를 배우는 것은 어렵습니다. 24시간 안에 자바를 배울 수는 없습니다. 기술을 연마하세요. 시간을 내어 업무를 배우고 탁월하게 수행하십시오.

코드에서 SOLID 원칙을 사용하는 방법

출처: Cleanthecode 신뢰할 수 있는 코드를 작성하려면 SOLID 원칙이 필요합니다. 어느 시점에서 우리 모두는 프로그래밍 방법을 배워야 했습니다. 그리고 솔직해지자. 우리는 바보였습니다. 그리고 우리의 코드는 동일했습니다. SOLID가 있어서 다행입니다. 커피 브레이크 #85.  제가 힘들게 배운 세 가지 Java 교훈입니다.  코드에서 SOLID 원칙을 사용하는 방법 - 2

견고한 원칙

그렇다면 SOLID 코드는 어떻게 작성하나요? 실제로는 간단합니다. 다음 다섯 가지 규칙을 따르기만 하면 됩니다.
  • 단일 책임 원칙
  • 개방-폐쇄 원리
  • Liskov 교체 원리
  • 인터페이스 분리 원리
  • 종속성 반전 원리
괜찮아요! 이 원칙은 보기보다 훨씬 간단합니다!

단일 책임 원칙

로버트 C. 마틴(Robert C. Martin)은 자신의 책에서 이 원칙을 다음과 같이 설명합니다. “클래스를 변경하는 이유는 단 하나여야 합니다.” 두 가지 예를 함께 살펴보겠습니다.

1. 하지 말아야 할 것

사용자가 다음 작업을 수행할 수 있도록 하는 User 라는 클래스가 있습니다 .
  • 계정 등록
  • 로그인
  • 처음 로그인할 때 알림을 받습니다.
이제 이 클래스에는 몇 가지 책임이 있습니다. 등록 프로세스가 변경되면 User 클래스도 변경됩니다. 로그인 프로세스나 알림 프로세스가 변경되는 경우에도 마찬가지입니다. 이는 클래스가 오버로드되었음을 의미합니다. 그에게는 책임이 너무 많습니다. 이 문제를 해결하는 가장 쉬운 방법은 User 클래스가 클래스 결합만 담당하도록 책임을 클래스로 옮기는 것입니다 . 프로세스가 변경되면 변경해야 하는 명확하고 별도의 클래스가 하나 있습니다.

2. 해야 할 일

새로운 사용자에게 알림을 표시해야 하는 클래스 FirstUseNotification 을 상상해 보세요 . 이는 세 가지 기능으로 구성됩니다.
  • 알림이 이미 표시되었는지 확인
  • 알림 표시
  • 알림을 이미 표시된 것으로 표시
이 수업을 변경해야 할 여러 가지 이유가 있나요? 아니요. 이 클래스에는 새로운 사용자에게 알림을 표시하는 명확한 기능이 하나 있습니다. 이는 클래스를 변경해야 할 이유가 하나 있다는 것을 의미합니다. 즉, 이 목표가 변경되는 경우입니다. 따라서 이 클래스는 단일 책임 원칙을 위반하지 않습니다. 물론 몇 가지 변경될 수 있는 사항이 있습니다. 알림이 읽음으로 표시되는 방식이나 알림이 표시되는 방식이 변경될 수 있습니다. 하지만 수업의 목적이 명확하고 기본적이므로 괜찮습니다.

개방-폐쇄 원리

개방-폐쇄 원칙은 Bertrand Meyer에 의해 만들어졌습니다. "소프트웨어 객체(클래스, 모듈, 기능 등)는 확장을 위해 열려야 하지만 수정을 위해서는 닫혀 있어야 합니다." 이 원리는 실제로 매우 간단합니다. 소스 코드를 변경하지 않고도 새로운 기능을 추가할 수 있도록 코드를 작성해야 합니다. 이렇게 하면 수정된 클래스에 종속된 클래스를 변경해야 하는 상황을 방지하는 데 도움이 됩니다. 그러나 이 원칙은 실행하기가 훨씬 더 어렵습니다. Meyer는 상속 사용을 제안했습니다. 그러나 그것은 강한 연결로 이어집니다. 이에 대해서는 인터페이스 분리 원칙과 종속성 역전 원칙에서 논의하겠습니다. 그래서 Martin은 다형성을 사용하는 더 나은 접근 방식을 생각해 냈습니다. 이 접근 방식은 기존 상속 대신 추상 기본 클래스를 사용합니다. 이러한 방식으로 구현이 필요하지 않은 동안 상속 사양을 재사용할 수 있습니다. 인터페이스를 한 번 작성한 다음 닫아서 변경할 수 있습니다. 그런 다음 새 함수는 이 인터페이스를 구현하고 확장해야 합니다.

Liskov 교체 원리

이 원칙은 프로그래밍 언어와 소프트웨어 방법론에 대한 공헌으로 Turing Award를 수상한 Barbara Liskov가 고안했습니다. 그녀의 기사에서 그녀는 자신의 원칙을 다음과 같이 정의했습니다. "프로그램의 개체는 프로그램의 올바른 실행에 영향을 주지 않고 해당 하위 유형의 인스턴스로 대체 가능해야 합니다." 프로그래머로서 이 원리를 살펴보겠습니다. 정사각형이 있다고 상상해 보세요. 직사각형일 수도 있는데, 정사각형은 직사각형의 특별한 모양이기 때문에 논리적으로 들립니다. 이것이 Liskov 대체 원칙이 구출되는 곳입니다. 코드에서 직사각형이 나타날 것으로 예상되는 모든 곳에 정사각형이 나타날 수도 있습니다. 이제 사각형에 SetWidthSetHeight 메서드가 있다고 상상해 보세요 . 이는 사각형에도 이러한 방법이 필요하다는 것을 의미합니다. 불행하게도 이것은 말이 되지 않습니다. 이는 여기서 Liskov 대체 원칙을 위반했음을 의미합니다.

인터페이스 분리 원리

모든 원칙과 마찬가지로 인터페이스 분리의 원칙은 생각보다 훨씬 간단합니다. "여러 개의 클라이언트별 인터페이스가 하나의 범용 인터페이스보다 낫습니다." 단일 책임 원칙과 마찬가지로 목표는 부작용과 필요한 변경 횟수를 줄이는 것입니다. 물론, 누구도 의도적으로 그러한 코드를 작성하지 않습니다. 하지만 만나기는 쉽습니다. 이전 원리의 제곱을 기억하시나요? 이제 계획을 실행하기로 결정했다고 상상해보십시오. 직사각형에서 정사각형을 생성합니다. 이제 우리는 사각형이 setWidthsetHeight를 구현하도록 강제하고 있는데 이는 아마도 아무 것도 하지 않을 것입니다. 그렇게 하면 너비와 높이가 우리가 예상한 것과 다르기 때문에 무언가가 깨질 수 있습니다. 운 좋게도 이는 이제 직사각형을 사용할 때마다 정사각형의 사용을 허용하므로 더 이상 Liskov 대체 원칙을 위반하지 않는다는 것을 의미합니다. 그러나 이는 새로운 문제를 야기합니다. 이제 우리는 인터페이스 분리 원칙을 위반하고 있습니다. 우리는 파생 클래스가 사용하지 않기로 선택한 기능을 구현하도록 강제합니다.

종속성 반전 원리

마지막 원칙은 간단합니다. 상위 수준 모듈은 재사용이 가능해야 하며 하위 수준 모듈 변경으로 인해 영향을 받아서는 안 됩니다.
  • A. 상위 수준 모듈은 하위 수준 모듈에 의존해서는 안 됩니다. 둘 다 추상화(예: 인터페이스)에 의존해야 합니다.
  • B. 추상화는 세부사항에 의존해서는 안 됩니다. 세부 사항(구체적인 구현)은 추상화에 따라 달라져야 합니다.
이는 상위 수준 모듈과 하위 수준 모듈을 분리하는 추상화를 구현하여 달성할 수 있습니다. 원리의 이름은 의존성의 방향이 변한다는 것을 암시하지만, 사실은 그렇지 않습니다. 단지 그들 사이에 추상화를 도입함으로써 종속성을 분리합니다. 결과적으로 다음 두 가지 종속성을 갖게 됩니다.
  • 추상화에 따른 상위 수준 모듈
  • 동일한 추상화에 따른 저수준 모듈
이것은 어렵게 보일 수 있지만 실제로는 개방/폐쇄 원칙과 Liskov 대체 원칙을 올바르게 적용하면 자동으로 발생합니다. 그게 다야! 이제 SOLID를 뒷받침하는 5가지 핵심 원칙을 배웠습니다. 이 5가지 원칙을 사용하면 코드를 멋지게 만들 수 있습니다!
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION