Browse Tag

Java

레저큐 인턴기 6편 – 백엔드 개발 도전! (2)

웹API 컨트롤러의 레이어드 아키텍쳐

전편에서 이어집니다…

가자고 웹API의 컨트롤러는

  • Controller 레이어: 가장 상위에서 유저의 리퀘스트(request)를 받고 리스폰스(response)를 돌려줌.
  • Service 레이어: Controller 밑에서 실질적인 비지니스 로직의 구현을 담고 있음.
  • Repository 레이어: DB와의 인터페이스를 담당.

이상의 세개 레이어(layer)로 구성되어 있다. 이렇게 하나의 시스템을 여러개의 레이어(층)로 분리하고 차곡 차곡 쌓아 올라가는 식으로 개발하는 방식을 ‘레이어드 디자인 패턴(Layered Design Pattern)’이라고 부르는데, 소프트웨어 엔지니어링에서 가장 보편적인 디자인 패턴 중 하나이다.

여러가지 이유가 있겠지만 이렇게 레이어를 나누는 가장 중요한 이유는

  1. 규모가 큰 시스템을 여러개로 모듈화하여 한 모듈의 문제가 시스템 전체로 번져나가는 것을 막음과 동시에 각 모듈을 쉽게 교체하기 위함이자, (종속성의 최소화, 재사용성)
  2. 오로지 하나의 이슈를 해결하도록 분리된 각 층을 추상화함으로써 컴포넌트 간에 인터페이스를 쉽게 하고 개발을 용이하게 하기 위함이다. (표준의 지원)

Screenshot 2016-09-06 14.59.49.png
가자고 웹API의 컨트롤러는 Controller, Service, Repository 등의 레이어로 구성된 레이어드 아키텍쳐(Layered Architecture)를 따른다.

레이어드 아키텍쳐 관점에서 개발 미션 바라보기

이쯤에서 나의 개발 미션이 무엇이었는지 다시 떠올려보자.

입력한 조건에 따라 DB에서 서비스 운영에 필요한 통계자료를 추출하는 웹 API를 새로 만들기

개발 미션을 가자고 웹API 컨트롤러의 레이어드 스트럭쳐 관점에서 바라보면 구현 단계를 가장 밑에서부터 네단계로 쌓아 올라가는 것으로 압축할 수 있다.

  1. Model 클래스 구현: 쿼리로 조회한 데이터를 담을 모델 클래스 만들기.
  2. Repository 구현: 데이터 추출 SQL 쿼리를 자바로 구현하기.
  3. Service 구현: 비지니스 로직을 코드로 구현하기. 즉, Repository에서 쿼리로 가져온 데이터들을 어떻게 가공하고 Controller에 다시 돌려줄 것인지 고민하기.
  4. Controller 구현: API에 URL 자원을 RESTFul하게 할당하고 유저의 리퀘스트 처리. 리스폰스 리턴.

사실 Repository 레이어에서 DB로부터 데이터를 가져오는 것을 빼고는 복잡할 것이 없는 작업이다.

  • Model 클래스에서는 내가 필요한 정보(DB의 필드에 해당한다)가 무엇인지 고민하고 이 값들을 담을 수 있는 변수들을 멤버로 가지고 있는 클래스들을 새로 만들어주면 된다. (클래스 만들기는 자바의 기본!)
  • Service에서는 Repository에서 돌려준 데이터들의 묶음(Collector: List, Map 등…)을 엑셀 템플릿과 엑셀 라이브러리를 이용해서 엑셀 파일로 출력해주면 된다. (엑셀 라이브러리만 가져다 쓰면 된다!)
  • Controller는 사실상 스프링 프레임워크에서 제공되는 라이브러리들을 이용하면 자바 어노테이션과 몇 줄 안되는 코드로 구현이 가능하다. (스프링이 알아서 다 해준다…)

자 그렇다면 이번 개발 미션의 핵심이 무엇인지 드러난다. Repository에 SQL SELECT 쿼리를 자바로 구현하는 것이 이번 개발 미션에서 가장 중요한 부분이다.

JOOQ로 SQL SELECT 쿼리를 자바로 구현하기

가자고 어플리케이션은 DB 인터페이스에 JPA(Java Persistence API: 자바 영속성 API)와 JOOQ(Java Object Oriented Querying)를 이용한다.

JPA는 자바 진영의 표준 ORM(Object-relational mapping) 기술로, 관계형 DB를 객체지향패러다임 언어인 자바에서 쓰기 위한 맵핑을 담당하는 기술이다. 표준 영속성(여기서 영속성이란 프로그램이 종료되어도 사라지지 않는 속성, 즉 DB 데이터의 속성을 의미한다) API인 만큼 자바를 이용해서 백엔드 개발을 하는 개발자들이 익히 잘 알고 있는 기술이다.

반면에 JOOQ는 다소 생소한 이름일 수 있다. 스위스 취리히 소재의 스타트업 Data Geekery이 운영하고, 개발하고 있는 JOOQ는, SQL 쿼리를 자바로 구현해야 하는 상황에 이용하는 라이브러리이다. 즉, 미리 작성해 놓은 SQL 쿼리를 그대로 자바로 옮기고 싶다거나, 아니면 JPA로는 구현이 힘든 매우 복잡한 SQL 쿼리를 짜야하는 경우에 멋진 대안이 된다. (참고로 이런 목적으로 사용하는 또 다른 라이브러리 중에 유명한 것이 iBatis 란 기술이다. 가자고 시스템에도 iBatis를 사용한 적이 있었지만 지금은 대부분 JOOQ로 대체하고 있다.)

좀 더 와닿는 코드 예제로 살펴보자. 백엔드 어플리케이션에서 아래와 같은 SQL 쿼리를 사용할 일이 있다고 가정하자. (코드 출처는 wikipedia.org – Java Object Oriented Querying)

위의 SQL 쿼리는 STATUS가 ‘SOLD OUT’인 BOOK 들의 AUTHOR 정보를 가져오는 테이블이다. JOOQ를 이용해서 자바로 변환하면, 이렇게 짤 수 있다.

JOOQ를 이용하면, SQL 문법과 상당히 유사한 자바 코드로 SQL 쿼리를 작성 할 수 있다. 자 그렇다면 이제 JOOQ를 이용해서 내가 작성해두었던 SQL SELECT 쿼리들을 자바로 옮길 차례다.

keep-calm-and-code-on
나우 잇츠 타임 포 코딩!

하나만 간단히 살펴보자. 가자고 서비스 운영에 필요한 정보 중에 특정 아이템을 구매한 고객들의 이름, 이메일, 전화번호를 알아야 하는 경우가 있었다. 이 리스트를 추출하기 위해서 아래와 같은 SQL SELECT 쿼리를 작성했었다.

이 것을 새로 생성한 Repository 클래스의 public List<FrontUser> getItemUserStat(List<String> itemCode) 메서드에 구현했다.

잠시 코드를 살펴보자. 먼저 해당하는 SQL SELECT 쿼리를 JOOQ를 이용해서 만들고 나서, fecthInto(FrontUser.class) 메서드를 호출했다. 이 메서드는 FrontUser 라는 모델 클래스의 리스트(List<FrontUser>)를 리턴하는 것을 확인 할 수 있다. 따라서 이 메서드에, 조회하고 싶은 아이템들의 itemCode를 List<String> 로 전달하면 해당하는 사용자의 정보를 FrontUser 모델 클래스의 리스트로 얻을 수 있다.

이렇게 JOOQ로 쿼리를 짜면 쉽고 간단하게 쿼리를 만들 수 있다. IDE의 자동완성 기능의 도움을 받는다면 일일이 JOOQ의 문서나 매뉴얼을 뒤져보지 않아도 그 자리에서 바로 SQL 쿼리를 자바로 옮길 수 있다.

이렇게 완성된 Repository 메서드를 이제는 거꾸로 Service에서 이용하고, 마지막으로 Controller에서 Service 메서드를 호출하면 SQL로 DB에 쿼리를 날렸을 때와 동일한 데이터를 자바 객체 묶음(Collector: List, Map 등등)으로 얻을 수 있었다. Controller의 각 메서드에 이제 RESTFul 한 URL을 할당해주기만 하면 웹 API가 완성된다!

새로운 세계

그렇게 작성한 나의 첫 백엔드 코드를 커밋(Git Commit)하고, 마침내 나의 풀리퀘스트(Pull Request)가 master 브랜치에 머지(merge) 되는 순간, 새로운 세계가 열렸다! 얕게는 매일 SQL 쿼리를 짜고 DB에서 데이터를 긁어오는 반복 노동으로부터의 해방된 것이고, 깊게는 백엔드 개발의 프로세스에 대한 경험을 해보게 된 것이다. 무엇보다 기뻤던 것은 드디어 가자고 프로젝트에 유의미한 기여를 할 수 있게 되었다는 사실이었다.

물론 이 것은 시작일 뿐이었다. 작동하는 웹 API를 만들어보기는 했지만, 웹 어플리케이션이라는 것이 무엇인지, 어떻게 동작하는지 제대로 이해하게 된 것도 아니고, Spring, JOOQ, JPA와 같은 기술들의 문서를 숙지하고 개발한 것도 아니고, 더군다나 효율적인 개발 프로세스대로 개발을 해본 것도 아니다. 그렇지만 내가 만든 API가 제대로 가자고 웹 어플리케이션 위에서 동작한 것을 확인했을 때의 즐거움이란 어마어마한 것이었다. 하하! 드디어 나도 이제 웹개발자구나! 🙂

여튼 이렇게 웹 개발의 ‘ㅇ’도 모르고 있었던 내가 DB를 다루고 관리하는 일부터 시작해서 백엔드 어플리케이션을 개발하는 백엔드 개발자의 업무를 경험해보기 까지 1달이 좀 못되는 시간이 걸렸다. 물론 그때는 몰랐다. 백엔드 개발에 더 익숙해지는데까지는 훨씬 더 많은 시간이 걸릴 것이라는 것을…

백엔드 개발 도전! 편 끝. 레저큐 인턴기는 계속 이어집니다…
By EastskyKang

레저큐 인턴기 5편 – 백엔드 개발 도전기 (1)

신이라면 믿겠지만 아니라면 데이터를 가져와야 합니다.

인터넷에서는 사용자의 결정과 행동을 추적하여 누적해두고 의미 있는 정보로 ‘데이터화’하는 것이 가능하다. 그리고 이런 데이터는 웹 쇼핑몰이라는 가상의 매장을 만들어놓고 상품을 판매하는 이커머스 서비스에게는 매우 강력한 무기가 된다. 웹사이트에서 어떤 사용자 경험성(UX, User Experience)을 제공할지, 어떤 상품을 노출할지, 어떤 가격으로 판매할지 등을 결정할 때 사용자의 구매 패턴과 행동 데이터들을 이용할 수 있고, 이를 통해 서비스의 매출을 극대화 시킬 수 있기 때문이다.

데이터를 기반으로 이커머스 서비스는 어떤 유저에게, 어떤 상품을, 어떤 가격으로(논쟁거리가 되기도 한다!) 노출할지 개인화할 수 있어서, 경제학에서 말하는 이상적인 판매 전략을 실현하여 큰 이익을 낼 수 있다. 따라서 데이터화와 분석은 이커머스가 기존의 상거래 시스템과 차별화되는 가장 주목할만한 요소 중 하나이며 기업의 성장과 직결되는 문제이다.

bigdata
빅데이터! 빅데이터! 빅데이터! 인터넷 세계에서 유저들이 만들어 내는 수많은 데이터들을 수집하고 잘 분석하는 것은 이제 이커머스 서비스에 필수적인 역량이 되었다. (이미지 출처 : LinkedIn post by Amarnath K Mattad)

이커머스 서비스는 사용자의 구매 패턴과 행동을 통계내고 분석하는 툴 혹은 시스템을 잘 구축해야 한다. 물론 사용자와의 인터랙션을 추적하는데 이용하는 Google Analytics과 같은 툴들도 훌륭하긴 하지만 아무래도 DB에서 가져오는 통계보다는 훨씬 부정확하고, 이커머스 운영의 의사결정에 활용하기 위한 정보들을 얻기에는 기능이 부족하다.

가자고 개발팀은 얼마 전부터 엘라스틱서치라는 검색엔진/데이터 저장소를 이용해서 매출과 실시간 판매 데이터등을 통계내고 비주얼라이제이션 하는 시스템을 구축하고 있다. 하지만 서비스 초창기에는 실시간으로 서비스 데이터를 수집하고 분석하는 시스템을 구축할 시간이 없었다. 대신 필요한 데이터가 있을 때 마다 SQL(Structured Query Language)코드로 직접 데이터베이스(DB)에서 판매 데이터나 매출 정보들을 긁어오는 ‘노동집약적인 방식'(^^;)으로 데이터를 수집하고 분석했다.

SQL
SQL은 관계형 데이터베이스 시스템을 관리하고, 조건에 맞는 데이터를 질의(Query) 하는데 이용되는 표준 언어이다.

나의 첫 백엔드 개발

나같은 새내기 개발자에게는 이런 작업이 좋은 기회가 될 수도 있다. DB에서 원하는 데이터를 가져오는 작업을 통해 시스템과 DB의 구조에 대해서 파악할 수 있었기 때문이다. 이커머스 서비스는 DB의 구조와 관계가 매우 복잡한데, 원하는 데이터를 얻기 위해서 어떻게 SQL 쿼리를 짤지 고민하는 것이 이 구조를 이해하는데 좋은 연습이 된다.

하지만 며칠이 지나자 통계를 얻기 위해서 SQL을 짜고, DB에 쿼리를 날리고, 얻어낸 데이터를 다시 스프레드시트에 정리하는 반복적인 작업들이 귀찮아지기 시작했다. 조금씩 조건이 바뀔 뿐, 매일 같이 반복적인 작업을 하는 것일 뿐인데, 수작업으로 SQL을 작성하고, 쿼리를 날리고, 데이터를 스프레드시트 포맷에 정리하는 일련의 일들이 생각보다 훨씬 번거롭고 지루했던 것이다.

이걸 자동화 할 수 있는 방법은 없을까? 당연히 있다. 가자고 백엔드 어플리케이션에 아예 이 통계를 내주는 기능을 추가하고 API를 만들어서 관리자 사이트에서 필요한 데이터의 종류와 검색 조건을 입력하면 해당하는 데이터를 엑셀로 정리해서 다운받을 수 있게 하면 된다.

자, 그렇다면 이제 드디어 첫 백엔드 개발을 시작할 때가 되었다. 일찍이 난무하는 자바 어노테이션(Java Annotation)들과 복잡한 구조에 기죽어서 포기했었던 백엔드 개발에 다시 도전하게 된 것이다.

그 첫번째 미션은 바로 입력한 조건에 따라 DB에서 서비스 운영에 필요한 통계자료를 추출하는 웹 API를 새로 만들기!

백엔드와 프론트엔드

여기서 잠깐 백엔드(Back-end)나 프론트엔드(Front-end)라는 용어를 정리하고 넘어가자. 위키피디아에 따르면 백엔드와 프론트엔드는 이렇게 정의되어 있다.

In software engineering, front end and back end distinguish between the separation of concerns between the presentation layer (the front end) – which is the interface between the user – and the data access layer (the back end). The front and back ends may be distributed among one or more systems.

갓 위키피디아 님에 따르면, 어플리케이션에서 엔드 유저의 눈에 보이지 않는 쪽을 백엔드(back end), 반대로 웹 브라우저 등을 통해서 유저에게 보여지는 쪽을 프론트엔드(front end)라고 한단다. 즉, 브라우저에서 유저와 인터랙션 하는 웹사이트의 UI를 구현하는 것은 프론트엔드 개발이고, 그 UI에 데이터를 뿌려주고, 사용자의 요청을 받아 비지니스 로직대로 처리할 수 있도록 구현하는 것은 백엔드 개발이라고 할 수 있다.

프론트엔드 개발자는 JavaScript, HTML, CSS 등을 이용해서 사용자 친화적인 UI(User Interface)를 구성하는데 주안점을 둔다. 반면에 백엔드 개발자는 사용하는 웹 프레임워크에 따라 Java(Spring, Spark…) 혹은 Python(Django, Flask …) 혹은 JavaScript(NodeJS) 혹은 Ruby(Ruby on Rails)를 이용해서 개발한다. 백엔드 개발자들이 가장 중요하게 생각하는 것은 웹 어플리케이션의 성능과 안정성, 그리고 아키텍쳐이다.

최근엔 백엔드, 프론트엔드, DB 관리, 서버 관리 등 웹 개발에 필요한 기술 전반을 모두 다룰 수 있는 풀스택 개발자(Full-stack developer)들도 시장에서 각광을 받고 있지만, 스타트업의 개발자들은 보통 백엔드 개발 전문 혹은 프론트엔드 개발 전문으로 역할을 나눈다. 백엔드 개발과 프론트엔드 개발은 요구하는 기술이 다르고 초점을 맞추는 것도 다르기 때문에 철저히 전문화된 개발자를 필요로 하기 때문이다.

web dev
웹 개발자들에게 요구되는 기술들. 보통은 한 명의 개발자가 이 모든 것을 다 하기 쉽지 않기 때문에 프론트엔드 개발, 백엔드 개발로 역할을 나누고는 한다. 좀 더 자세히 알고 싶다면 Michael Wales가 Udacity 블로그에 쓴 3 Web Dev Careers Decoded: Front-End vs Back-End vs Full Stack을 참조하자.

가자고는 8명의 개발자가 함께 개발하고 있는데 프론트엔드에 치중하는 개발자가 2명, 백엔드에 치중하는 개발자가 4명, 그리고 그 모두를 개발하고 있는 풀스택 개발자가 1명 있다.

나머지 1명인 나의 역할? 어디에도 속하지 않지만 어디에도 속할 수 있으니 풀스택 개발자라고 치자. ^^

백엔드 개발하기

백엔드 어플리케이션은 서버에서 데이터를 비지니스 로직대로 처리하고 처리한 데이터를 웹 API(Application Program Interface)를 통해 제공하는 역할을 한다. 프론트엔드 개발자들이 우리 눈에 보이는 UI 껍데기를 만들어 놓으면 백엔드 개발자가 개발한 API로 데이터와 리소스들을 불러와서 뿌려주는 것이다.

가자고는 백엔드 프레임워크로 스프링 프레임워크 (Spring Framework)를 쓴다. 스프링은 Java 진영에서 가장 널리 쓰이는 오픈소스 웹 어플리케이션 프레임워크이다. 엔터프라이즈 용으로 개발된 웹 프레임워크이기 때문에 방대하고 기능이 매우 다양하며 유지보수가 쉬운 (쉽나…?) 프레임워크이다. 다만 방대하고 기능이 많은 만큼 진입장벽이 높고, 배우기 어려운 프레임워크이기도 하다.

spring_framework
스프링 프레임워크는 이제는 사실상 Java진영의 엔터프라이즈급 표준 웹 어플리케이션 프레임워크가 되었다. 무엇이든지 할 수 있는 스위스칼 같은 존재랄까…

가자고는 일반적으로 웹 어플리케이션에 적용되는 MVC 디자인 패턴에 따라서 어플리케이션을 뷰(View), 컨트롤러(Controller), 모델(Model)로 나눠서 개발을 하고 있다. 각 컴포넌트들의 역할을 정리해보면…

  • 모델: 관계형 DB에서의 테이블에 대응되는 개념이다. 즉 데이터 스키마라고 볼 수 있다.
  • 뷰: 브라우저를 통해서 유저의 눈에 보이는 UI를 구현한 소스코드와 그 컨테이너에 해당한다.
  • 컨트롤러: 유저의 요청사항(request)를 받아 데이터를 의뢰하고, 가공 후 웹 API의 형태로 돌려준다(response).

뷰가 프론트엔드(JSP, JS, HTML, CSS 등에 해당)에 해당한다고 하면 컨트롤러와 모델이 백엔드에 해당하는 컴포넌트이다. 따라서 웹 API를 만들기 위해서는 처리하고자 하는 데이터들의 스키마(설계도 혹은 틀 쯤으로 생각할 수 있다)를 모델로 정의 및 구현하고, 컨트롤러에서 요청을 받아 비지니스 로직대로 처리하여 돌려 줄 수 있어야 한다.

다만 가자고에서는 (일반적인 웹 어플리케이션들이 그렇듯이) 컨트롤러의 역할을 서비스(Service: 비지니스 로직을 구현한 부분. 추상화된 ‘모델’을 이용하여 이를 가공하고 컨트롤러에 돌려준다.), 리포지토리(Repository: DB와의 인터페이스를 담당한다. ‘모델’의 추상화를 담당하는 부분이다.) 등의 여러개의 레이어로 나누어 개발하고 있다. 이런 구조를 레이어드 아키텍쳐(Layered architecture) 라고 부른다….

mvc_pattern
MVC 패턴의 개념도. (이미지 출처: 구루비 위키)

spring_layered_archtecture
스프링 프레임워크의 레이어드 아키텍쳐. (이미지 출처: petrikainulainen.net)

아, 새로운 용어들이 나오니까 벌써 머리가 아파온다. 자세한 내용은 생략하자… 다음에 좀 더 자세히 얘기할 기회가 있을 것 같다. 여튼 백엔드 개발은 이렇게 복잡한 디자인과 구조, 그리고 웹을 이루는 기반 기술들에 대한 이해를 요구하는 작업이다. 하지만 (이 무렵의) 나에게 이러한 지식과 이해 따위가 있었을리 없다. 그래서…

일단 만들고 보자

그 많은 지식들과 기반 기술들에 대해서 습득할 시간도 없고, 방대한 개발 문서들을 읽을 수도 없다. 숙련된 개발자는 새로운 프레임워크나 라이브러리들을 익히기 위해서 개발문서를 차분히 읽어보면서 기능과 개념을 공부하겠지만 나와 같은 새내기 개발자에겐 아무리 잘 쓰여진 개발문서도 “흰것은 종이요 검은 것은 글자” 일 뿐이다. ‘내가 무엇을 아는지, 무엇을 모르는지’ 전혀 모르는 상태이기 때문이다.

그래서 일단 만들고 싶은 것을 땅바닥에 헤딩해가며 만들어보기로 했다. 그러다 보면 내가 무엇을 공부해야 하는지, 개발 문서에 잔뜩 써져 있는 외계어들이 무엇을 의미하는 것인지도 점점 알게 될 것이다. 다행히 이미 가자고 팀 개발자들이 짜놓은 API 코드가 아주 많으니 참고할 코드도 충분하다.

시작부터 험난하지만 이렇게 나의 첫 백엔드 개발이 시작되었다. 구체적으로 어떻게 했는지 계속 이어서 쓰고 싶지만 이미 충분히 길어졌으니 아 힘들다… 좋은 글귀를 남겨놓고 이번 편을 마무리하자. 다음편에서 계속…

정신적인 존재로서 드넓은 물을 향해 가자.
너는 거기에서 종횡무진으로 살아가며,
마음대로 움직이리라.

요한 볼프강 폰 괴테, “파우스트” 중

By EastskyKang

왜 자바에서 static의 사용을 지양해야 하는가?

자바에서 데이터를 가공하거나 특정 메서드를 수행할 때 새로운 클래스를 만들어서 이를 인스턴스화 해서 쓸건지 아니면 static 으로 쓸건지 고민하게 될 때가 있다. 사실 후자는 객체지향적 관점에서 그리 좋은 선택은 아니다. Vamsi Emani라는 프로그래머가 stack overflow에 남긴 질문 Why are static variables considered evil? 과 가장 많은 지지를 받은 두개의 답변을 번역했다.


Q by V. Emani

I am a Java programmer who is new to the corporate world. Recently I’ve developed an application using Groovy and Java. All through the code I’ve used quite a good number of statics. I was asked by the senior technical lot to cut down on the number of statics used. I’ve googled about the same, and I find that many programmers are fairly against using static variables.

저는 현업에 갓 뛰어든 자바 프로그래머입니다. 근래에 Groovy와 Java를 이용하는 어플리케이션을 개발하고 있습니다. 그동안 자바로 개발할 때 “static” 변수(그리고 static 메소드)를 꽤나 많이 이용하는 습관을 가지고 있었습니다. 근데 제 시니어는 static 의 개수를 줄이라고 말합니다. 그 이유가 궁금해서 구글에 검색을 해봤는데 많은 자바 프로그래머가 static 을 사용하는 것을 꺼린다는 것을 발견했습니다.

I find static variables more convenient to use. And I presume that they are efficient too (please correct me if I am wrong), because if I had to make 10,000 calls to a function within a class, I would be glad to make the method static and use a straightforward class.methodCall() on it instead of cluttering the memory with 10,000 instances of the class, right?

사실 저는 static 을 이용하는 것이 보다 편하고 효율적이라고 생각합니다. (제가 틀린 부분이 있으면 지적해주세요!) 어떤 클래스 내에서 만번의 함수 호출을 하게 된다면 그 함수를 static으로 만들어서 class.methodCall()의 형태로 사용하는 것이 만개의 인스턴스를 생성해서 메모리를 어지럽히는 것보다 훨씬 낫지 않을까요?

Moreover statics reduce the inter-dependencies on the other parts of the code. They can act as perfect state holders. Adding to this I find that statics are widely implemented in some languages like Smalltalk and Scala. So why is this oppression for statics prevalent among programmers (especially in the world of Java)?

PS: please do correct me if my assumptions about statics are wrong.

게다가 static 변수는 코드의 상호의존성(inter-dependency)을 줄여준다고 생각합니다. 상태를 저장하는데 있어서 static 변수들은 아주 유용하게 사용될 수 있습니다. 사실 static은 자바 뿐만 아니라 Smalltalk나 Scala 와 같은 프로그래밍 언어에서도 널리 이용되고 있습니다. 근데 왜 유독 자바 프로그래밍 세계에선 개발자들이 static의 사용을 꺼리는 걸까요?

PS: static 변수에 대한 제 생각에 틀린 부분을 지적해주시면 감사하겠습니다.


A by J. Skeet

Static variables represent global state. That’s hard to reason about and hard to test: if I create a new instance of an object, I can reason about its new state within tests. If I use code which is using static variables, it could be in any state – and anything could be modifying it.

Static 변수는 global state(역주: 전역 상태. 프로그램 혹은 한 모듈 전체의 상태)를 상징합니다. Global state는 추론과 테스트가 매우 까다롭습니다. 가령 코드에서 static 변수를 사용한다고 하면, 이 변수의 상태는 코드 여러 부분에서 영향을 받을 수 있게 되고 따라서 변화를 추적하기가 어려워집니다. 반면에 이를 객체화하여 인스턴스로 생성하면 테스트 중에 그 변수가 어떤 상태를 가지고 있는지 추론하는 것이 보다 간단해집니다.

I could go on for quite a while, but the bigger concept to think about is that the tighter the scope of something, the easier it is to reason about. We’re good at thinking about small things, but it’s hard to reason about the state of a million line system if there’s no modularity. This applies to all sorts of things, by the way – not just static variables.

프로그래머로서 제가 그동안 경험해온 바에 따르면 큰 개념에 대해서 그리기 위해선 일단 이해하고자 하는 범위를 좁혀 쉽게 추론할 수 있어야 합니다. 일반적으로 우리는 작으면 작을수록 그 대상을 쉽게 이해합니다. 다시 말해, 모듈화를 제대로 하지 않는다면 백만 줄 짜리 시스템의 상태에 대해서 추론하는 것은 굉장히 어려운 일입니다. 이것은 단순히 static 변수 뿐만 아니라 모든 프로그래밍 이슈에 대해서 적용할 수 있는 중요한 사실입니다.


A by A. Lockwood & J. Brown

Its not very object oriented: One reason statics might be considered “evil” by some people is they are contrary the object-oriented paradigm. In particular, it violates the principle that data is encapsulated in objects (that can be extended, information hiding, etc). Statics, in the way you are describing using them, are essentially to use them as a global variable to avoid dealing with issues like scope. However, global variables is one of the defining characteristics of procedural or imperative programming paradigm, not a characteristic of “good” object oriented code. This is not to say the procedural paradigm is bad, but I get the impression your supervisor expects you to be writing “good object oriented code” and you’re really wanting to write “good procedural code”.

첫째로, static은 객체 지향적이지 않습니다: 개발자들이 static 변수를 ‘악’으로 규정하는 이유는 static 변수가 객체 지향의 패러다임과 상반되기 때문입니다. 특히나 static 변수는, 각 객체의 데이터들이 캡슐화되어야 한다는 객체지향 프로그래밍의 원칙(역주: 한 객체가 가지고 있는 데이터들은 외부에서 함부로 접근하여 수정할 수 없도록 해야 한다는 원칙)에 위반됩니다. 질문자께서 스스로 설명했듯이 static은 스코프(역주: 한 변수가 유효한 범위)를 고려할 필요가 없는 경우, 즉 전역 변수를 사용할 때에 유용합니다. 이는 절차지향적 프로그래밍 혹은 명령형 프로그래밍(역주: C가 대표적인 절차지향적, 명령형 프로그래밍 언어이며 Java 역시 큰 범위에서 절차지향적, 명령형 프로그래밍 언어라고 할 수 있다.)에서 매우 중요한 개념입니다. 하지만 이 것이 객체지향의 관점에서 좋은 코드라고 얘기하기는 힘듭니다. 절차지향 패러다임이 나쁘다는 것이 아닙니다. 다만, 당신의 시니어는 당신이 “객체지향적으로 좋은 코드”를 짜기를 바라는 것입니다. 반대로 당신은 “절차지향적으로 좋은 코드”를 짜기를 원하는 것이라고 말할 수 있을 것입니다.

There are many gotchyas in Java when you start using statics that are not always immediately obvious. For example, if you have two copies of your program running in the same VM, will they shre the static variable’s value and mess with the state of each other? Or what happens when you extend the class, can you override the static member? Is your VM running out of memory because you have insane numbers of statics and that memory cannot be reclaimed for other needed instance objects?

사실 자바에서 static을 사용하기 시작하면 예측이 어려운 문제가 많아지게 됩니다. 예를 들어서 하나의 가상머신에서 어떤 프로그램 두 카피가 돌고 있다고 가정해봅시다. 만약 이 두 카피가 동일한 static 변수를 공유하게 된다면, 서로의 상태에 영향을 주게 되지 않을까요? 더불어서 오버라이딩을 할 수 없는 static 멤버들 때문에 클래스를 확장하는게 어려워질 것입니다. 뿐만 아니라 지나치게 많은 static 변수를 사용하게 되면 이들로부터 메모리 회수를 할 수 없어서 가상머신이 메모리 부족을 겪게 될 것입니다.

Object Lifetime: Additionally, statics have a lifetime that matches the entire runtime of the program. This means, even once you’re done using your class, the memory from all those static variables cannot be garbage collected. If, for example, instead, you made your variables non-static, and in your main() function you made a single instance of your class, and then asked your class to execute a particular function 10,000 times, once those 10,000 calls were done, and you delete your references to the single instance, all your static variables could be garbage collected and reused.

객체의 라이프타임: 추가로, static 변수는 프로그램이 실행되고 있는 내내 살아있게 됩니다. 즉, 그 클래스를 이용한 작업을 끝내더라도 static 변수가 점유하고 있는 메모리는 garbage collector(역주: 사용하지 않는 메모리를 회수하는 기능)에 의해서 회수되지 않게 됩니다. 반대로, 프로그래머가 그 변수를 인스턴스화 해서 main() 함수 내에서 하나의 인스턴스로 생성하게 되면, 그리고 그 인스턴스에게 만번의 함수 호출을 시키게 되면 그 만번의 함수 호출이 끝난 후 인스턴스는 소멸됩니다. 따라서 메모리를 훨씬 절약할 수 있게 됩니다.

Prevents certain re-use: Also, static methods cannot be used to implement an interface, so static methods can prevent certain object oriented features from being usable.

static은 재사용성이 떨어집니다: 또한, static 메서드는 interface를 구현하는데 사용될 수 없습니다. 즉 static 메서드는 프로그래머가 (재사용성을 높여주는)이러한 자바의 유용한 객체지향적 기능들을 사용하는 것을 방해합니다.

Other Options: If efficiency is your primary concern, there might be other better ways to solve the speed problem than considering only the advantage of invocation being usually faster than creation. Consider whether the transient or volatile modifiers are needed anywhere. To preserve the ability to be inlined, a method could be marked as final instead of static. Method parameters and other variables can be marked final to permit certain compiler optimizations based on assumptions about what can change those variables. An instance object could be reused multiple times rather than creating a new instance each time. There may be complier optimization switches that should be turned on for the app in general. Perhaps, the design should be set up so that the 10,000 runs can be multi-threaded and take advantage of multi-processor cores. If portability isn’t a concern, maybe a native method would get you better speed than your statics do.

static의 대안들: 프로그래머에게 효율(여기서는 속도)이 가장 중요한 문제여서 객체를 생성할 때 마다 생기는 사소한 불이익에도 민감한 상황일 수 있습니다. 이 경우에도 여전히 static 대신에 다른 방법들을 사용하는 것이 가능합니다. 먼저 “transient”나 “volatile”과 같은 제어자(modifier)를 쓸 수 있는지 먼저 고려해봅니다. 실행 속도를 빠르게 해주는 메소드 인라이닝(역주: 실제 메소드를 호출하지 않고 바로 결과값을 돌려주는 방식)을 위해 “final” 메서드를 사용하는 것도 생각해볼 수 있습니다. 또한 메서드 파라미터들과 변수들이 final로 선언되면 컴파일러 단에서의 최적화 작업이 가능해집니다. 인스턴스를 사용할 때마다 새로 생성하는 대신에 여러번 재사용할 수도 있습니다. 아마도 컴파일러 단의 최적화 작업이 switches that should be turned on for the app in general. 어쩌면 멀티스레드를 이용해서 멀티코어 프로세스의 장점을 극대화하기 위해선 이런 디자인이 필수적일 수도 있습니다. 이식성(역주: 다른 플랫폼으로 쉽게 옮길 수 있는 특성)이 중요한 것이 아니라면, native 메서드를 사용해서 static을 사용하는 것보다 더 빠르게 만들 수도 있을 것입니다.

If for some reason you do not want multiple copies of an object, the singleton design pattern, has advantages over static objects, such as thread-safety (presuming your singleton is coded well), permitting lazy-initialization, guaranteeing the object has been properly initialized when it is used, sub-classing, advantages in testing and refactoring your code, not to mention, if at some point you change your mind about only wanting one instance of an object it is MUCH easier to remove the code to prevent duplicate instances than it is to refactor all your static variable code to use instance variables. I’ve had to do that before, its not fun, and you end up having to edit a lot more classes, which increases your risk of introducing new bugs…so much better to set things up “right” the first time, even if it seems like it has its disadvantages. For me, the re-work required should you decide down the road you need multiple copies of something is probably one of most compelling reasons to use statics as infrequently as possible. And thus I would also disagree with your statement that statics reduce inter-dependencies, I think you will end up with code that is more coupled if you have lots of statics that can be directly accessed, rather than an object that “knows how to do something” on itself.

만약 여러개의 인스턴스를 만드는 것을 피하고 싶다면 싱글톤 디자인 패턴을 이용하는 것이 훌륭한 대안이 될 수 있습니다. 싱글톤 디자인은 (싱글톤을 제대로 구현했다는 전제하에) 스레드 안정성을 가지고, lazy-initialization(역주: 객체가 필요할 때마다 만들어 쓰는 기법)을 허용하며, 객체가 사용될 때마다 제대로 초기화 된다는 것을 보장합니다. 뿐만 아니라 서브 클래싱(sub-classing) 기법을 가능하게 하고, 테스트와 리팩토링이 매우 용이합니다. 다음의 상황을 가정해봅시다. 프로그래밍을 하다가 어느 시점에서 지금까지의 설계를 바꿔야겠다는 생각이 들게 되면 두말할 것도 없이 하나의 인스턴스를 수정하는 것이 모든 static 변수들을 리팩토링 하는 것보다 훨씬 편할 것입니다. 사실 static을 사용하다가 refactoring을 해야하는 상황은 매우 흔한 일입니다. 그것은 유쾌하지 않은 일일 뿐 아니라 훨씬 많은 클래스를 수정하게 만들기도 합니다. 이렇게 또다시 클래스들을 수정하다보면 새로운 버그를 만들어낼 소지가 매우 커집니다. 이런 상황을 피하기 위해서 처음에 “제대로”(위에서 언급한 방식들대로) 디자인하여 코딩하는 것이, 그 방식이 몇가지 단점을 가지고 있는 것 처럼 보여도 훨씬 나은 선택입니다. 사실 이런 끔찍한 재수정 작업이 요구될지도 모른다는 소지가 제가 static을 되도록 쓰지 않으려는 가장 큰 이유 중 하나입니다. 정리하자면, 저는 질문자께서 static이 코드의 상호의존성(inter-dependency)을 줄여준다고 말하신 것에 동의할 수 없습니다. 인스턴스화 되어 있는 객체들을 쓰지 않고 static 변수에 직접 접근하는 방식으로 코드를 짜다보면, 결국 작성한 모듈들이 서로 더 많이 엮이는 (바람직하지 않은) 상황에 처하게 될 것입니다.


가자고팀은 백엔드 어플리케이션을 Java로 개발하고 있다. Java로 개발을 하다보면 자주 쓰는 메서드들을 static (클래스 메서드)으로 선언하고자 하는 유혹이 생겨난다. 객체화를 할 필요도 없고 접근이 훨씬 용이하기 때문이다. 하지만 위의 두 개발자의 답변대로 static의 남용은 프로그램의 상태를 추정하기 어렵게 만들고 결과적으로 객체지향적이지 않은 코드를 작성하게 만든다.

물론 static을 ‘evil’이라고 규정하고 있는 이들의 의견에 전적으로 동의하는 것은 아니다. 그러나 필자도 개발 중에 static 을 빈번하게 사용하면 겪게 되는 문제들을 경험해본 적이 있다. ‘좋은 코드’라는 것에 결코 절대적인 기준이 있는 것은 아니지만, 객체지향적 프로그래밍의 원칙들을 되새겨볼때 분명 static의 사용에 심사숙고할 필요가 있어보인다.

By Eastsky Kang