[노개북 1기] TIL (2022.02.10)

yourjin·2022년 2월 26일
0

read.log

목록 보기
22/37
post-thumbnail

TIL (2022.02.10)

DAY 19

🔖 오늘 읽은 범위 : 10장, 클래스


😃 책에서 기억하고 싶은 내용을 써보세요.

  • 클래스는 작아야 한다!

    이전 내용 복습
    *- 클래스를 만들 때 첫 번째 규칙은 크기다. 클래스는 작아야 한다

    • 일반적으로 메서드가 변수를 더 많이 사용할수록 메서드와 클래스는 응집도가 더 높다*
    • 응집도를 유지하면 작은 클래스 여럿이 나온다
      • 큰 함수를 작은 함수 여럿으로 나누기만 해도 클래스 수가 많아진다.

        • 예를 들어, 변수가 아주 많은 큼 함수가 하나 있다. 큰 함수 일부를 작은 함수 하나로 빼내고 싶은데, 빼내려는 코드가 큰 함수에 정의된 변수 넷을 사용한다. 그렇다면 변수 네 개를 새 함수에 인수로 넘겨야 옳을까?
        • 전혀 아니다! 만약 네 변수를 클래스 인스턴스 변수로 승격한다면 새 함수는 인수가 필요없다. 그만큼 함수를 쪼개기 쉬워진다.
      • 불행히도 이렇게 하면 클래스가 응집력을 잃는다. 몇몇 함수만 사용하는 인스턴스 변수가 점점 더 늘어나기 때문이다.

      • 그런데 잠깐만! 몇몇 함수가 몇몇 변수만 사용한다면 독자적인 클래스로 분리해도 되지 않는가? 당연하다. 클래스가 응집력을 잃는다면 쪼개라!

        결론
        - 큰 함수를 작은 함수 여럿으로 쪼개라
        - 몇몇 인스턴스 변수와 함수를 묶어 독자적인 클래스로 분리하라

      • PrintPrimes 프로그램 리팩터링

        • 가장 먼저 눈에 띄는 변화가 프로그램이 길어졌다는 사실이다.
        • 길이가 늘어난 이유는 여러 가지다.
          1. 리팩터링한 프로그램은 좀 더 길고 서술적인 변수 이름을 사용한다.
          2. 리팩터링한 프로그램은 코드에 주석을 추가하는 수단으로 함수 선언과 클래스 선언을 활용한다.
          3. 가독성을 높이고자 공백을 추가하고 형식을 맞추었다.
        • 원래 프로그램은 세 가지 책임으로 나눠졌다.
        • 재구현이 아니다! 프로그램을 처음부터 다시 짜지 않았다.
          • 가장 먼저, 원래 프로그램의 정확한 동작을 검증하는 테스트 슈트를 작성했다. 그런 다음, 한 번에 하나씩 수 차례에 걸쳐 조금씩 코드를 변경했다. 코드를 변경할 때마다 테스트를 수행해 원래 프로그램과 동일하게 동작하는 지 확인했다.
          • 조금씩 원래 프로그램을 정리한 결과 최종 프로그램이 얻어졌다.
  • 변경하기 쉬운 클래스
    • 대다수 시스템은 지속적인 변경이 가해진다. 그리고 뭔가 변경할 때마다 시스템이 의도대로 동작하지 않을 위험이 따른다.
    • 깨끗한 시스템은 클래스를 체계적으로 정리해 변경에 수반하는 위험을 낮춘다.
    • 변경이 필요해 ‘손대야’ 하는 클래스
      public class Sql {
      	public Sql(String table, Column[] columns)
      	public String create()
      	public String insert(Object[] fields)
      	public String selectAll()
      	public String findByKey(String keyColumn, String keyValue) 
      	public String select(Column column, String pattern)
      	public String select(Criteria criteria) 
      	public String preparedinsert()
      	private String columnlist(Column[] columns)
      	private String valueslist(Object[] fields, final Column[] columns)
      	private String selectWithCriteria(String criteria)
      	private String placeholderlist(Column[] columns)
      }
      • update 문을 지원할 시점이 오면 클래스에 ‘손대어’ 고쳐야 한다. 문제는 코드에 ‘손대면’ 위험이 생긴다는 사실이다.
      • 새로운 SQL 문을 지원하려면 반드시 Sql 클래스에 손대야 한다. 또한 기존 SQL 하나를 수정할 때도 반드시 Sql 클래스를 고쳐야 한다. (…) 이렇듯 변경할 이유가 두 가지이므로 Sql 클래스는 SRP를 위반한다.
      • 단순히 구조적인 관점에서도 Sql는 SRP를 위반한다. 메서드를 쭉 훑어 보면 selectWithCriteria라는 비공개 메서드가 있는데, 이 메서드는 select문을 처리할 때만 사용한다.
      • 하지만 실제로 개선에 뛰어드는 계기는 시스템이 변해서라야 한다.
      • 하지만 클래스에 손대는 순간 설계를 개선하려는 고민과 시도가 필요하다.
    • 닫힌 클래스 집합
      abstract public class Sql {
      		public Sql(String table, Column[] columns)
      		abstract public Strign generate();
      }
      
      public class CreateSql extends Sql {
      		public CreateSql(String table, Column[] columns)
      		@Override public String generate()
      }
      
      public class SelectSql extends Sql {
      		public SelectSql(String table, Column[] columns)
      		@Override public String generate()
      }
      
      public class InsertSql extends Sql {
      		public InsertSql(String table Column[] columns, Object[] fields)
      		@Override public String generate()
      		private String valuesList(Object[] fields, final Coiumn[] columns)		
      }
      
      public class SelectWithCriteriaSql extends Sql {
      		public SelectWithCriteriaSql(String table, Column[] columns, Criteria criteria)
      		@Override public String generate()
      }
      
      public class FindByKeySql extends Sql {
      		public FindByKeySql(String table, String keyColumn, String keyValue)
      		@Override public String generate()
      }
      
      public class PreparedInsertSql extends Sql {
      		public PreparedInsertSql(String table, Column[] columns)
      		@Override public String generate()
      		private String placeholderList(Column[] columns)
      }
      
      public class Where {
      		public Where(String criteria)
      		public String generate()
      }
      
      public class ColumnList {
      		public ColumnLsit(Column[] columns)
      		public String generate()
      }
      • update 문을 추가할 때 기존 클래스는 변경할 필요가 전혀 없다는 사실 역시 중요하다! update 문을 만드는 논리는 Sql 클래스에서 새 클래스 UpdateSql을 상속받아 거기에 넣으면 그만이다. update 문을 지원해도 다른 코드가 망가질 염려는 전혀 없다.
      • 재구성한 Sql 클래스는 세상의 모든 장점만 취한다!
        • 우선 SRP를 지원한다.
        • 여기다 객체 지향 설계에서 또 다른 핵심 원칙인 OCP(Open-Closed Principle)도 지원한다. OCP란 클래스는 확장에 개방적이고 수정에 폐쇄적이어야 한다는 원칙이다.
      • 새 기능을 수정하거나 기존 기능을 변경할 때 건드릴 코드가 최소인 시스템 구조가 바람직하다.
    • 변경으로부터 격리
      • 요구사항은 변하기 마련이다. 따라서 코드도 변하기 마련이다

      • 객체 지향 프로그래밍 입문에서 우리는 구체적인(concreate) 클래스와 추상(abstract) 클래스가 있다고 배웠다.

      • 상세한 구현에 의존하는 클라이언트 클래스는 구현이 바뀌면 위험에 빠진다. 그래서 우리는 인터페이스와 추상 클래스를 사용해 구현이 미치는 영향을 격리한다.

      • 상세한 구현에 의존하는 코드는 테스트 하기가 어렵다.

        • 예를 들어, Portfolio 클래스를 만든다고 가정하자. 그런데 Portfolio 클래스는 외부 TokyoStockExchange API를 사용해 포트폴리오 값을 계산한다.
        • 따라서 우리 테스트 코드는 시세 변화에 영향을 받는다. 5분마다 값이 달라지는 API로 테스트 코드를 짜기란 쉽지 않다.
      • Portfolio 클래스 리펙터링

         public interface StackExchange {
         		Money currentPrice(String symbol);
         }
        • Portfolio 클래스에서 TokyoStockExchange API를 직접 호출하는 대신 Stock Exchange라는 인터페이스를 생성한 후 메서드 하나를 선언한다.

           public Portfolio {
           	private StockExchage exchange;
           	public Portfolio(StockExchange exchage) {
           		this.exchange = exchange;
           	}
           }
        • 다음으로 StockExchange 인터페이스를 구현하는 TokyoStockExchange 클래스를 구현한다.

        • 또한 Portfolio 생성자를 수정해 StockExchange 참조자를 인수로 받는다.

          public class PortfolioTest {
          		private FixedStockExchangeStub exchange;
          		private Portfolio portfolio;
          		
          		@Before
          		protected void setUp() throws Exception {
          				exchange = new FixedStockExchangeStub();
          				exchange.fix("MSFT", 100);		
          				portfolio = new Portfolio(exchange);
          		}
          		
          		@Test
          		public void GivenFiveMSFTTotalShouldBe500() throws Exception {
          				portfolio.add(5, "MSFT");
          				Assert.assertEquals(500, portfolio.value());
          		}
          }
        • 이제 TockyoStockExchange 클래스를 흉내내는 테스트용 클래스를 만들 수 있다.

        • 우리 테스트용 클래스는 단순히 미리 정해놓은 표 값만 참조한다. 그러므로 우리는 전체 포트폴리오 총계가 500불인지 확인하는 테스트 코드를 작성할 수 있다.

        • 위와 같은 테스트가 가능할 정도로 시스템의 결합도를 낮추면 유연성과 재사용성도 더욱 높아진다. 결합도가 낮다는 소리는 각 시스템 요소가 다른 요소로 부터 그리고 변경으로부터 잘 격리되어 있다는 의미다.

        • 이렇게 결합도를 최소로 줄이면 자연스럽게 또 다른 클래스 설계 원칙인 DIP(Dependency Inversion Principle)를 따르는 클래스가 나온다. 본질적으로 DIP는 클래스가 상세한 구현이 아니라 추상화에 의존해야 한다는 원칙이다.

        결론
        - 우리가 개선한 Portfolio 클래스는 TokyoStockExchange라는 상세한 구현 클래스가 아니라 StockExchange 인터페이스에 의존한다.
        - 이와 같은 추상화로 실제로 주가를 얻어오는 출처나 얻어오는 방식 등과 같은 구체적인 사실을 모두 숨긴다

🤔 오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요

  • 어제와 오늘은 객체 지향의 핵심이라고 볼 수 있는 클래스에 대한 내용을 공부했다. 어제 읽은 부분은 하나의 책임만 맡을 수 있도록 함수/클래스를 쪼개는 방법에 대해 배웠다면, 오늘은 추상화를 통해 구현에 의존하지 않는 재사용성이 높은 클래스를 만드는 방법에 대해 배웠다. 특히 오늘 읽은 추상화 부분은 이름처럼 추상적이라 아직 100% 완벽하게 적용할 수 있겠다는 자신은 없다. 하지만 앞으로 내가 구현할 수많은 코드를 통해 더욱 잘 이해하고 활용할 수 있지 않을까 기대해본다!

🔎 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.

  • 하지만 실제로 개선에 뛰어드는 계기는 시스템이 변해서라야 한다?
  • 구체적인(concreate) 클래스와 추상(abstract) 클래스
  • 결합도

소감 3줄 요약

  • 클래스가 응집력을 잃는다면 쪼개라!
  • 깨끗한 시스템은 클래스를 체계적으로 정리해 변경에 수반하는 위험을 낮춘다.
  • 상세한 구현에 의존하는 클라이언트 클래스는 구현이 바뀌면 위험에 빠진다. 그래서 우리는 인터페이스와 추상 클래스를 사용해 구현이 미치는 영향을 격리한다.
profile
make it mine, make it yours

0개의 댓글