{{ label!='' ? 'Label : ' : (q!='' ? '검색 : ' : '전체 게시글') }} {{ label }} {{ q }} {{ ('('+(pubs|date:'yyyy-MM')+')') }}

[Java] 자바로 프로그래밍 입문하기: 2.2. 라이브러리와 클라이언트 (1)

라이브러리와 클라이언트

  여러분이 이제껏 작성했던 프로그램들은 하나의 .java 파일에 작성되었습니다. 규모가 작으니 상관은 없지만, 규모가 큰 프로그램에서 모든 코드를 한 파일 안에 적는 것은 제한적이고 불필요합니다. 다행히도 자바에서 다른 파일에 정의된 메소드를 사용하는 것은 굉장히 쉬운 일입니다. 이는 우리에게 두 가지 중요한 능력을 부여해줍니다.

  첫째, 코드 재사용을 가능하게 합니다. 이미 작성된 프로그램의 코드를 복사하는 대신 단순히 참조하는 것만으로도 재사용을 할 수 있습니다. 코드를 정의하고 재사용하는 것은 현대 프로그래밍의 필수적인 부분입니다.

  둘째, 모듈화 프로그래밍을 가능하게 합니다. 2.1절에서 한 것처럼 정적 메소드를 이용해 작업을 나누는 것뿐만 아니라, 응용프로그램의 필요에 따라 서로 다른 파일에서 관리될 수 있습니다. 모듈화 프로그래밍은 프로그램의 부분부분을 독립적으로 개발, 컴파일, 디버깅을 할 수 있도록 합니다. 

  이는 각 모듈들에 대해 그 세부사항을 걱정할 필요 없이 나중에 가져다 쓰기만 하면 된다는 것입니다. 예를 들어, 여러분들은 실제 터미널에 어떤 과정으로 글자가 출력되는지 자세히 알 필요 없이 StdOut.print()를 통해 쉽게 출력할 수 있었습니다. 실제 내부의 동작은 굉장히 복잡하죠.

  자바의 Math 라이브러리와 우리의 입출력을 위한 Std* 라이브러리는 우리가 이제껏 써왔던 라이브러리들입니다. 여러분들은 나만의 라이브러리를 정의하는 것이 얼마나 쉬운 것인지 알게 될 것입니다. 라이브러리를 정의할 수 있다는 것은, 그 라이브러리를 사용함으로써 많은 프로그램의 복잡한 작업들을 구성할 수 있다는 것입니다.


  여지껏 자바 프로그램은 단순히 어떤 순서로 배치된 일련의 구문들에 불과하였습니다. 자바 프로그램은 메소드의 집합으로 구성된 클래스라고 생각하는 것, 더 나아가서 여러분들은 자바 프로그램을 메소드의 집합으로 이루어진 독립적 모듈인 클래스의 집합으로 생각할 준비가 될 것입니다.

  각 메소드는 다른 클래스의 메소드를 호출할 수 있습니다. 곧, 모든 코드는 클래스로 묶인 메소드의 네트워크로서 서로를 호출하며 상호작용 하는 것이라고 볼 수 있습니다. 여러분들은 앞으로 프로그래밍을 할 때, 각 작업을 독립적인 클래스로 나누어 어떻게 복잡한 프로그램을 관리할지에 대한 고민을 시작해야 합니다.

  

다른 프로그램의 정적 메소드를 사용하기

  다른 곳에 정의된 클래스의 정적 메소드를 사용하기 위해서는, 우리가 이제껏 해왔던 것처럼 사용하면 됩니다. StdOut.printf()나 StdAudio.play()처럼요.

  • 해당 클래스들이 같은 디렉토리에 있어야 합니다.
  • 메소드를 호출하기 위해서는, 해당하는 클래스 이름과 온점을 찍어서 구분합니다.

  예를 들어, <프로그램 2.1.2, Gaussian>를 봐봅시다. 한 메소드를 보니, 이 메소드를 정의하기 위해서는 제곱근 함수가 필요합니다. 우리가 이미 <프로그램 2.1.1, Newton>에서 정의했던 sqrt() 메소드를 사용하고 싶다고 해보죠. 그러기 위해선 단순히 Gaussian.java 파일과 Newton.java 파일을 같은 경로에 위치하게 하고, 클래스 이름과 함께 sqrt()를 호출하면 됩니다.

  만약 자바의 구현을 사용하고 싶다면, Math.sqrt()를 호출해도 됩니다. 하지만 이번에는 우리가 구현해본 것을 사용해보고 싶기 때문에, Newton.sqrt()를 호출해봤습니다. 더 나아가, 우리는 Gaussian.pdf()나 Gaussian.phi()를 호출할 수도 있겠죠. <SATmyYear>는 <Gaussian>과 <Newton>의 메소드를 호출하게 됩니다.

  여러 메소드가 있는 클래스를 각각 여러 파일로 정의하는 것은, 우리 프로그래밍의 방식을 완전히 뒤바꿉니다. 일반적으로, 이런 접근법을 모듈화 프로그래밍이라 합니다. 


public 키워드

  우리는 모든 메소드를 public으로 정의해왔었습니다. 이 키워드는 해당 메소드를 다른 파일에서도 접근할 수 있도록 만듭니다. private라는 키워드도 있는데, 이는 다른 곳에서 접근할 수 없도록 만듭니다. 하지만 이 시점까지 여러분들은 그럴 필요는 없었죠. 이는 챕터 3에서 자세히 다뤄볼 것입니다.


각 모듈은 클래스이다

  한 파일 안에 있는 모든 코드를 우리는 모듈이라고 합니다. 쉽게 말하면 .java 파일 하나가 모듈 하나인 셈이죠. 자바에서는 이를 클래스라고 말하며, 클래스의 이름은 파일 이름과 같아야 합니다. 이번 챕터에서는 정적 메소드를 담고 있는 클래스들을 다룹니다. 좀 더 자세한 구조들은 챕터 3에서 다뤄보도록 합시다.


.class 파일

  여러분이 프로그램을 컴파일 하면, 자바 컴파일러는 해당 이름으로 된 .class 파일을 만들게 됩니다. 이 때 여러분들은 해당 클래스의 메소드들을 이용할 수 있으며, 심지어 해당 클래스의 .java 파일이 없어도 상관 없습니다. 즉, .class 파일만 있다면 여러분들은 언제나 그 모듈을 사용할 수 있습니다. 다만, 여러분들이 버그를 발견하게 될 때를 대비해서 .java 파일을 남겨두어야겠죠?


필요에 의한 컴파일

  여러분이 프로그램을 컴파일 할 때, 자바 컴파일러는 여러분의 프로그램이 실행되기 위해 필요한 것들을 전부 컴파일 합니다. 예를 들어 <Gaussian>에서 Newton.sqrt()를 호출하므로, 여러분이 javac Gaussian.java 라고 입력하게 된다면 컴파일러는 Newton.java 또한 이전에 컴파일 된 적이 있는지 확인할 것입니다. 이 경우 Newton.class가 생성되어 있는지 확인하겠죠.

  이것을 알려주는 이유는, 여러분이 <Gaussian>을 컴파일 하였어도 Newton.sqrt() 메소드는 단순히 참조해서 이용하는 것에 불과하다는 것을 인지해야 하기 때문입니다. 만약 여러분이 <Newton>을 수정하고 싶어서 수정하고 컴파일을 했다면, 그 즉시 <Newton>을 사용하는 모든 클래스들에게 영향이 간다는 사실을 명심하세요.


여러 개의 main 메소드

  확인 하고 넘어가야 할 사항 중 하나는, main() 메소드를 지닌 클래스가 여러 개인 경우입니다. 예를 들어 <SATmyYear>, <Newton>, <Gaussian>은 각자 main() 메소드를 지녔죠. 여러분이 프로그램을 실행하는 규칙에 대해 생각해본다면, 이는 딱히 어려운 상황은 아닙니다. 터미널에 java 명령어 다음에 클래스 이름을 명시해, 어떤 main() 정적 메소드를 실행할 건지 명확히 하기 때문이죠. 

  우리는 main()을 각 대부분의 클래스에 작성하고, 이 클래스가 제대로 동작하는지 테스트하고 디버그 할 것입니다. 이렇게 한다면 우리가 <SATmyYear>를 실행시키고 싶으면, java SATmyYear 라고 입력하면 되고, <Newton>이나 <Gaussian>을 디버그 해보고 싶으면, java Newton 혹은 java Gaussian 이라 입력하면 될 뿐입니다.


  만약 여러분이 프로그램을 작성하면서, 해당 프로그램이 나중에 다시 사용될 것임을 전제하고 작성하게 된다면, 여러분은 곧 수많은 종류의 유용한 프로그램들을 쌓아갈 수 있을 것입니다. 모듈화 프로그래밍은, 수많은 컴퓨터 프로그램을 마치 우리 컴퓨터 환경의 부가적인 도구로 볼 수 있게 만들어줍니다.

  예를 들어, 앞서 <Gaussian>에서 우리는 수학 함수들을 구현했었습니다. 나중에 이것이 또 필요할 때, <Gaussian> 파일을 열어 코드를 직접 복사 및 붙여넣기 하는 것은 좋지 않습니다. 왜일까요? 이렇게 해도 분명 프로그램은 작동합니다. 하지만 이렇게 코드 복사본이 흩뿌려지게 된다면, 유지보수가 어려워집니다.

  만약 여러분이 나중에 해당 메소드를 변경하거나 확장하고 싶다면, 모든 복사본들에 대해 직접 수정해야 합니다. 대신 Gaussian.phi() 메소드를 사용하면 어떨까요? 변경이 필요하다면, <Gaussian>의 해당 메소드 부분만 수정하면 됩니다. 우리는 메소드를 쉽게 활용하면서도 유지보수까지 할 수 있는 두 마리 토끼를 잡는 셈이죠.

  앞으로, 우리는 합리적인 방법으로 모든 프로그램을 적당한 크기로 분할하고, 각 부분을 나중에 누군가가 사용할 수 있게끔 구현할 것입니다. 아마 대부분의 경우 그 누군가는 여러분이 될 것입니다. 또한 스스로에게 코드를 다시 작성하고 다시 디버깅하는 노력을 들이지 않아도 됨에 감사하게 될 것입니다.


라이브러리(Libraries)

  우리는 메소드들을 다른 프로그램이 쉽게 사용할 수 있도록 만들어진 모듈을 자주 사용하며, 이를 라이브러리라고 합니다. 자바 프로그래밍의 중요한 특성 중 하나는, 많고 많은 메소드들이 여러분들을 위해 미리 정의되어 있고, 수천 개의 자바 라이브러리들을 여러분들이 사용할 수 있다는 것입니다. 

  하지만 수많은 라이브러리를 우리가 실제로 이용할 능력이 되는가는 또 다른 문제겠죠. 너무 실망하지 마세요! 이번 챕터에서 우리는 이보다 더 중요한 사용자 정의(user-defined) 라이브러리를 만드는 법에 대해 배웁니다. 이는 다른 프로그램에서 사용될 수 있으면서 비슷한 메소드들을 하나로 묶은 클래스에 지나지 않습니다. 아무렴 자바 라이브러리라 한들 우리가 필요한 모든 메소드들을 구현할 수는 없으므로, 이를 정의할 수 있는 것은 굉장히 중요합니다.


클라이언트(Clients)

  주어진 메소드를 호출하는 프로그램을 클라이언트라고 말합니다. 어떤 클래스의 메소드가, 다른 클래스의 메소드를 호출할 때, 우리는 첫째 클래스를 둘째 클래스의 클라이언트라고 말합니다. 예를 들어, <Gaussian>은 <Newton>의 클라이언트입니다. 특정 클래스는 여러 클라이언트를 가질 수 있는데, 예를 들어 Math.sqrt()나 Math.Random()을 사용한 프로그램들은 전부 <Math>의 클라이언트입니다. 새롭게 정적 메소드나 클래스를 구현할 때, 여러분은 클라이언트를 위해 어떤 일들을 해야할지 확실히 알아야 합니다.



APIs

  프로그래머들은, 보통 클라이언트와 라이브러리 사이의 약속으로 해당 메소드들이 무엇을 하는지 확실히 명시해야 한다고 말합니다. 여러분이 클라이언트가 되기도 하고 라이브러리의 구현을 하기도 할 때, 여러분은 스스로와의 약속을 지켜야 하며 이는 디버깅을 할때 꽤나 도움이 됩니다.

  더욱 중요한 이유로는, 이런(메소드가 무엇을 하는지 명시하는) 접근은 코드 재사용을 가능케 합니다. 여러분은 <Std*>와 <Math>와 다른 자바 내장형 클래스들의 클라이언트로서 프로그램을 작성할 수 있었는데, 이는 메소드들을 사용할 수 있게끔 시그니처에 대한 정확한 명세가 있었기 때문입니다.

  이런 정보들을 application programming interface(API) 라고 합니다. 이는 사용자 정의 라이브러리를 작성할 때 중요합니다. API는 클라이언트가 코드를 직접 들여다볼 필요 없이 라이브러리를 사용할 수 있게 만듭니다. 여러분이 <Math>나 <Std*> 라이브러리를 사용했듯이 말입니다. API의 원칙은 "클라이언트들이 필요한 메소드만을 제공한다."입니다. 쓸데 없이 많은 메소드들로 구성된 API는 구현하기 부담될 것이고, 중요한 메소드가 부족한 API는 클라이언트에게 불편함만 줄 것입니다.


구현(Implementations)

  구현이라 함은 규칙에 맞는 라이브러리 이름을 지닌 .java 확장자 파일로, API에 있는 메소드들을 자바 코드로 나타내는 것입니다. 모든 자바 프로그램은 API의 구현이며, 구현 없는 API는 아무것도 아닙니다. 클라이언트 코드와 구현 코드를 분리하는 것은 우리에게 기존의 구현을 새로 만들거나 개선 할 때 쉽게 대체할 수 있도록 만듭니다.


  예를 들어, 가우시안(정규) 분포 함수를 생각해봅시다. 이는 자바의 Math 라이브러리에 있지 않으나, 많은 응용 프로그램에서 이를 필요로 한다면, 우리는 그것들을 라이브러리에 넣어 미래의 클라이언트 프로그램이 사용할 수 있도록 하고 API로 표현할수 있습니다.


  네 개의 정적 메소드를 구현하는 것은 <프로그램 2.1.2, Gaussian>에 있으며, 차후 정규 분포 함수를 활용할 때 <Gaussian> 라이브러리만 있으면 걱정할 필요가 없어집니다.


  API에는 어떤 정보들을 담아야 할까요? 이는 애매한 경계에 있으며 지금도 프로그래머들과 컴퓨터 과학자들 사이에서 뜨거운 감자입니다. API에 가능한 많은 정보들을 담을 수도 있죠. 하지만 생산적인 정보들을 담는 데에는 한계가 있습니다. 이 강의에서는 다음 설계 원칙을 따르도록 합시다. "클라이언트 프로그래머들에게 필요한 정보 그 이상은 제공하지 않는다."

  쉽게 말하자면 달갑지 않은 정보들은 굳이 적지 않아도 된다는 것입니다. 많은 프로그래머들은 구현 코드를 일일히 확인하여 이것이 어떻게 동작하는지에 대해 이해하려 합니다. 이는 사실 그렇게 좋은 습관은 아닙니다. 이렇게 하면 API에 명시되지 않은 행위에 의존하는 클라이언트 코드를 만들게 될 수 있고, 새로운 구현에서 제대로 동작하지 않을 수도 있습니다. 구현의 변경은 생각보다 자주 일어납니다. 예를 들어 자바 버전이 새로 나올 때마다, 각 라이브러리의 함수들 일부는 계속 구현이 변경되고 있습니다.

  종종 구현을 먼저 할 때도 있습니다. 작동하는 모듈을 구현해놨었는데, 나중에 보니 이것이 꽤나 유용했고, 해당 메소드들을 다른 프로그램에 사용하기 시작한 경우입니다. 이런 경우에 언젠가는 API를 조심스럽게 정의해보는 것이 현명한 행동입니다. 메소드가 재사용을 위해 설계되지 않았으므로, API를 명세함으로써 그러한 설계를 개선할 수도 있는 것입니다.

  이 절의 나머지는 라이브러리와 클라이언트의 예제들입니다. 우리의 목적은 라이브러리를 두 가지 측면에서 생각해보는 것입니다. 첫째, 라이브러리는 여러분의 클라이언트 프로그램을 만족스럽게 개발할 수 있게끔 더 풍성한 프로그래밍 환경을 제공합니다. 둘째, 라이브러리는 여러분들에게 공부할 수 있는 예제가 됩니다.


계속.

댓글

이 블로그의 인기 게시물

[코딩의탑] 4층: 툰 쉐이딩

[코딩의탑] 3층: 바다 렌더링

[코딩의탑] 5층: 포탈(Portal), 더 나아가기