노드에서 모듈이라는 개념은 노드로 개발한 애플리케이션을 이루는 기본 조각이라고 할 수 있다. 미리 만들어진 파트들을 조립해서 물건을 만드는 것처럼, 관련된 코드들을 모아서 캡슐화 해놓은 것을 모듈이라고 한다. 그리고 이런 여러가지 모듈들을 이용하면 프로젝트를 훨씬 더 수월하게 진행할 수 있다. 일반적으로 노드로 개발된 애플리케이션은 객체 지향의 개념이 아주 잘 녹아 있는 자바스크립트를 기반으로 하여, 파일과 1:1맵핑이 되는 다양한 모듈로 구성된다. 이는 노드를 개발할 때 개발자가 추가하는 모듈 뿐만 아니라 노드의 기능을 확장할 수 있도록 해주는 확장 모듈과 기본으로 제공하는 기능들에게도 해당되는 이야기다. 노드의 모듈화 개념 노드는 확장성을 위해 모듈의 구조를 이용하여 애플리케이션을 구성하도록 하고 있..
상속이라는 개념을 이론적으로 분명 학습했는데, 잘 안쓰다보니(직접 만들어 쓴적은 없다는 얘기... JpaRepository 인터페이스 같은 것은 자주 사용...) 그 개념을 자꾸 잊어먹어서 implements, extends 심지어 추상 클래스 ,메서드인(명칭만 기억하지 기능은 기억도 안나는 것 같다.. 반성한다) abstract 등 한번 기회 잡아서 다시 정리해야지하며 미뤘는데, 어쩌다보니 스터디원분이 상속 얘기를 꺼내면서, 매우 진땀을 흘렸다. 민망해서 밤에 이불킥 몇 번 하다가 내 선생님인 '구글'을 찾아봤다. 마침 간단하게 잘 요약해둔 블로그가있어서, 옮겨적어본다. 참고: https://velog.io/@hkoo9329/%EC%9E%90%EB%B0%94-extends-implements-%EC%B..
Node.js는 서버사이드 자바스크립트이며 구글의 자바스크립트 엔진인 V8을 기반으로 구성된 (일종의) 소프트웨어 시스템이다. 이벤트 기반으로 개발이 가능하며 Non-Blocking I/O를 지원하기 때문에 비동기식 프로그래밍이 가능하다. 이 때문에 I/O 부하가 심한 대규모 서비스를 개발하기 적합하다고 할 수 있다. (또한 자바스크립트의 표준 라이브러리(CommonJS)의 스펙을 따르고 있다) 노드의 탄생 배경 노드는 2009년 라이언 달(Ryan Dahl)이 고안해 낸 언어로 같은 해 JSConf EU 컨퍼런스에서 처음 발표돼었다. 노드가 개발된 배경과 목적은 다수의 연결을 효율적으로 관리하고 비용을 최소화할 수 있는 네트워크 소프트웨어를 개발하기 편리한 방법을 제공하기 위함이었다. 기존의 웹 애플리..
이동욱 님의 '스프링 부트와 AWS로 혼자 구현하는 웹 서비스' 책을 차근차근 따라가면서 웹 프로젝트를 진행하고 있는 중에 괄호안의 REST 규약이라는 단어의 정의를 잘 몰랐는데 웹 과정 수업을 들으면서 언뜻? 언급돼었던 기억이 났다. 생각이 난 김에 REST(?)란 무엇인지 알아보고 가는 시간을 가지기로 했다. REST API 웹 어플리케이션에서 제공하는 모든 자원을 URI를 할당하여 활용하는 방식 GET, POST, PUT, DELETE 가 대표적이다. 생겨난 이유 하드웨어와 소프트웨어의 발전으로 인해 프레임 워크 / 라이브러리의 발전과 앱이 등장했고, 클라이언트와 서버의 분리로 생겨났습니다. 하나의 어플리케이션에서 클라이언트와 서버를 관리하던 추세에서 앱의 등장이 있었고 확장성과 안정..
Equals와Hashcode, toString 등.. 기존의 정의되어있는 메소드를 오버라이딩을 하여 사용하는 경우가 많다. Equals와 toString은 어떤 용도로 사용하는지 알고있었지만, Hashcode의 경우 IntelliJ IDE의 자동완성 기능에서 종종 보았지만, 의미와 용도를 잘 모르고 지나치는 경우가 많았다. (부끄럽지만 알아볼 생각도 안했다) Equals와 Hashcode 메소드에 대해 알아보자 ! 아래의 코드와 같이 member1객체와 member2객체를 선언하고 객체를 비교하였을 때, 결과는 어떻게 될까? 답은 당연히 false일 것이다. 그 이유는 둘은 동일 객체가 아니기 때문이다. public class Test { public static void main(String[] arg..
기대하고 갈망했던 '우아한 테크 코스 2기'에 지원했다가 보기 좋게 떨어져 버렸다. 허탈함에 한동안 유튜브만 보면서 시간을 보냈다. 대책없이 이렇게 살면 안되겠다는 생각이 드는 찰나 지인(은인)으로부터 '생활코딩' 커뮤니티에 올라온 스터디 모집공고글을 캡쳐해서 보내줬다. 내용을 보아하니 스터디를 모집하시는 분도 우테코를 지원한 나의 동지였다. 왠지모를 동질감과 좌절하지 않고 바로 행동으로 옮기는 추진력을 보면서 나 자신이 약간 한심하게 느껴졌다. 그리고 스터디에 참가하고 싶다는 메일을 바로 작성했다. 그렇게 모집된 10명의 멤버들은 우테코 없는 우테코(=홍철 없는 홍철팀)를 목표로 우테코 1기 수강생분의 도움을 받아 일단 한달간(3주 or 4주) 우테코 1기 커리큘럼을 따라 프리코스 기간을 가지기로 했다..
프로그래밍 공부/Node.js
노드의 모듈
로ᄏl2020. 1. 21. 23:00
728x90
노드에서 모듈이라는 개념은 노드로 개발한 애플리케이션을 이루는 기본 조각이라고 할 수 있다.
미리 만들어진 파트들을 조립해서 물건을 만드는 것처럼, 관련된 코드들을 모아서 캡슐화 해놓은 것을 모듈이라고 한다. 그리고 이런 여러가지 모듈들을 이용하면 프로젝트를 훨씬 더 수월하게 진행할 수 있다.
일반적으로 노드로 개발된 애플리케이션은 객체 지향의 개념이 아주 잘 녹아 있는 자바스크립트를 기반으로 하여, 파일과 1:1맵핑이 되는 다양한 모듈로 구성된다. 이는 노드를 개발할 때 개발자가 추가하는 모듈 뿐만 아니라 노드의 기능을 확장할 수 있도록 해주는 확장 모듈과 기본으로 제공하는 기능들에게도 해당되는 이야기다.
노드의 모듈화 개념
노드는 확장성을 위해 모듈의 구조를 이용하여 애플리케이션을 구성하도록 하고 있다. 모듈은 애플리케이션을 이루는 기본 단위로 보통 다수의 클래스와 이를 통해 생성한 객체로 구성된다. 이런 모듈 단위로 구성된다는 것은 객체 지향 컨셉으로 애플리케이션이 구성된다는 것을 의미한다고 말할 수 있으며, 이는 앞서 설명한대로 노드가 CommonJS의 스펙을 대부분 준수하기 때문이다.
기본적으로 노드의 모듈은 자바스크립트 파일 하나와 1:1로 맵핑된다고 생각하면 된다. 이러한 모듈 형태는 NPM(뒤에서 소개)으로 쉽게 할 수 있는 노드 확장 모듈의 개발을 편리하게 해주며, 실제 우리가 애플리케이션을 개발할 때에도 소스 코드를 깔끔하게 구성할 수 있도록 해준다.
노드의 모듈은 CommonJS모듈 스펙으로부터 많은 영향을 받아 구현되었지만 완벽히 동일하지는 않다. 하지만 많은 유사점이 있으며, CommonJS 기반의 다른 시스템과 공유하고자 한다면 이 스펙에 대해 이해하고 있는 것이 도움이 될 것이다.
전역 객체의 개념
전역 객체는 어디에서나 사용할 수 있는 객체를 말한다. 클라이언트 자바스크립트에서는 window와 document 같은 객체를 전역 객체라고 말할 수 있는데, 우리가 alert()이라는 함수를 사용할 때 단순히 'alert('msg')'만 입력하더라도, 사실은 window라는 객체에 window.alert()과 같은 형태로 이미 포함되어 있으며, 전역 객체를 생략하여 사용하고 있는 것이다.
마찬가지로 서버 사이드에서 동작하는 노드의 경우에는 "global"이라는 전역 객체가 존재하고 있는데, 이 전역 객체에는 우리가 앞으로 모듈을 불러올 때 사용할 require()를 비롯하여 setTimeout(), console.log() 등을 포함하고 있다.
예로들어 아래의 코드는 살펴보자
require('./module')
global.require('./module')
위의 코드 2줄은 같은 의미이다. global이라는 전역 객체가 require()를 가지고 있기 때문에, 위의 두 표현이 모두 가능한 것이다. 전역 객체 global의 세부적인 내용에 대해서는 추후에 기본 모듈을 다룰 때 설명한다.
모듈의 종류
노드에서는 모듈의 종류를 크게 노드 설치 시 기본적으로 설치되어 있는지에 대한 유무를 통한 기본 모듈과 확장 모듈로, 단순히 자바스크립트로 작성하였는지, C/C++등의 다른 언어로 작성하고 빌드하여 개발하였는지에 따라 일반 모듈과 네이티브 모듈로, 그리고 지금 현재 웹 애플리케이션에만 사용하기 위해 설치했는지에 따라 글로벌 모듈과 로컬 모듈정도로 분류할 수 있다. 각 모듈의 종류를 알고 있는 것이 개발하는 것에 중요한 영향을 미치는 것은 아니지만 모듈을 로딩하고 체계적으로 NPM을 배포하기 위해서는 각 모듈의 특징 정도는 알아두는 것이 좋다.
기본 모듈과 확장 모듈
기본 모듈은 노드를 설치하면 기본으로 설치되는 모듈로서 파일의 입출력, 이벤트 관리, HTTP 프로토콜 관리 등에 관한 내용들로써 노드의 비동기 입출력 처리를 위한 기본적인 기능들을 제공하는 모듈이다. 이에 비해 확장 모듈은 노드의 기능을 확장하기 위한 것으로써 좀 더 쉬운 HTTP 서버 생성, 기본 파일 입출력에서 제공하지 않는 기능 추가 등 노드를 확장하고 더 편리하게 사용하기 위한 모듈이라고 말할 수 있다. 또한 확장 모듈은 나중에 소개할 express와 같은 모듈들처럼 하나의 프레임워크로서 제공되기도 하며, mongodb를 사용하기 위한 mongoose나 mongolian처럼 다른 기술을 손쉽게 끌어와 사용할 수 있게 도와주는 역할을 하기도 한다.
일반 모듈과 네이티브 모듈
일반 모듈과 네이티브 모듈은 개발 방법에 따라 나눌 수 있는 내용이다. 일반적으로 노드의 기본 모듈은 C/C++로 개발된 경우가 대부분이며 이렇게 네이티브 프로그래밍 언어인 C/C++로 노드의 모듈을 개발한 경우 네이티브 모듈이라고 한다. 확장 모듈 또한 네이티브 모듈로 개발할 수 있으며, .node라는 확장자를 가졌으면 네이티브 모듈로 컴파일 된 것이라고 보면 된다. 일반 모듈은 노드의 모듈 인터페이스를 통해 간단히 자바스크립트로 개발된 모듈이다. 만약 같은 기능을 수행하는 네거티브 모듈과 일반 모듈이 있다고 한다면 대체로 네이티브 모듈이 더 나은 성능을 보인다.
글로벌 모듈과 로컬 모듈
기본 모듈을 포함하여 어디서든 사용할 수 있도록 설치한 확장 모듈을 글로벌 모듈이라고 한다면, 해당 애플리케이션에서만 사용하기 위해 설치한 확장 모듈이나 개발자가 개발한 모듈을 로컬 모듈이라고 분류할 수 있다. 확장 모듈을 글로벌 모듈로 사용하려면 설치 시 특별한 옵션(-g)을 이용하면 된다.
복합 모듈
복합 모듈은 내부 모듈이나 데이터 파일, 템플릿, 테스트 코드 등을 포함하는 모듈을 말한다. 다양한 파일을 포함하기 때문에 모듈을 구성하는 단위가 폴더이며 내부적으로도 폴더를 가질 수 있다. require() 메소드가 이런 폴더 단위의 모듈을 인식하려면 index.js 파일이나 package.json 파일 중 하나가 반드시 있어야한다.
모듈 식별자
노드의 모듈 이름으로는 확장자 없는 전체 경로를 사용한다. 앞서 설명한 것처럼 모듈은 파일과 일대일로 대응하기 때문에 .js나 .node라는 확장자를 가지지만 모듈을 불러들일 때는 파일 이름만 사용하면 되기 때문이다. 모듈의 식별자는 크게 상대적 식별자와 절대적 식별자, 그리고 최상위 레벨 식별자 등 3가지가 있다.
상대적 식별자
상대적 식별자는 현재 위치를 기준으로 상대적인 기준에 있는 모듈을 찾는 식별자이다. 유닉스 시스템의 상대 경로와 유사하다고 할 수 있겠으나 ./를 생략하면 현재 위치에서 모듈을 찾지 않으므로 주의해야한다.
절대적 식별자
절대적 식별자도 마찬가지로 절대 위치를 기준으로 그 경로에 맞게 모듈을 찾는 식별자이며, 유닉스 시스템의 절대 경로와 비슷하다.
최상위 레벨 식별자
아무것도 입력하지 않고 모듈 이름만 입력하면 최상위 레벨 식별자로 인식하고 설치된 전체 확장 모듈이나 기본 모듈 중에서 해당 모듈 이름을 검색하여 로드한다.
require('sample_module');
여기에서 중요한 점은 sample_module이라는 모듈 식별자를 찾는 순서이다. 만약 sample_module이라는 모듈이 기본모듈에서 찾을 수 없는 모듈이라면 상위 디렉토리를 차례대로 탐색하게 된다.
require() 메서드와 module.exports
모듈 개념에서 require() 메서드와 module.exports가 왜 중요한 것일까?
먼저 클라이언트 측 자바스크립트와 비교를 해보겠다. 웹 브라우저에서는 HTML의 <script> 태그로 필요한 자바스크립트를 연결하고 호출하게 되어 있다. 다음의 코드는 HTML 파일이 자바스크립트를 로딩하는 예제이다.
이렇게 HTML 스크립트를 통해 명시된 자바스크립트 파일들은 웹 브라우저에 의해 로딩되며 서로 참조하거나 호출할 수 있다. 하지만, 노드는 HTML을 사용하지 않으므로 서로 다른 자바스크립트 파일들이 서로 참조하고 호출하는 방법이 필요하다.
require() 메서드는 모듈 식별자인 module.exports를 이용해 모듈이 제공하는 함수나 객체 등을 반환한다. 만약 불러온 모듈이 다른 모듈도 필요로 한다면 그 모듈도 같이 로드하도록 되어 있다. 이것은 노드 모듈의 핵심이다.
노드에서 하나의 자바스크립트 파일은 하나의 모듈이 될 수 있다는 것을 잘 알고 있다. 이때 자바스크립트 파일 내부에서 함수나 변수를 module.exports에 할당하면 외부에서 접근할 수 있다. B.js는 자바스크립트 작성된 모듈이다. 이 모듈은 name이라는 변수와 run(), stop()이라는 메서드를 가지고 있다.
A.js는 이러한 모듈B를 사용하고자 하는 자바스크립트 파일이다. 이 파일에서 moduleA라는 객체는 require() 메서드를 통해 생성된 모듈B의 인스턴스를 할당받는다. B.js에서는 module.exports를 통해 run()과 stop() 메서드를 외부에서 사용할 수 있도록 허용하고 있으므로 A.js에서 moduleB를 통해 run()과 stop() 메서드를 호출하여도 동작하게 된다. 다시 말해, 모듈을 사용하는 파일에서는 requir() 메서드로 다른 자바스크립트 파일을 불러와서 module.exports로 허용된 변수나 함수로 사용할 수 있다.
상속이라는 개념을 이론적으로 분명 학습했는데, 잘 안쓰다보니(직접 만들어 쓴적은 없다는 얘기... JpaRepository 인터페이스 같은 것은 자주 사용...) 그 개념을 자꾸 잊어먹어서 implements, extends 심지어 추상 클래스 ,메서드인(명칭만 기억하지 기능은 기억도 안나는 것 같다.. 반성한다) abstract 등 한번 기회 잡아서 다시 정리해야지하며 미뤘는데, 어쩌다보니 스터디원분이 상속 얘기를 꺼내면서, 매우 진땀을 흘렸다. 민망해서 밤에 이불킥 몇 번 하다가 내 선생님인 '구글'을 찾아봤다. 마침 간단하게 잘 요약해둔 블로그가있어서, 옮겨적어본다.
Node.js는 서버사이드 자바스크립트이며 구글의 자바스크립트 엔진인 V8을 기반으로 구성된 (일종의) 소프트웨어 시스템이다.
이벤트 기반으로 개발이 가능하며 Non-Blocking I/O를 지원하기 때문에 비동기식 프로그래밍이 가능하다. 이 때문에 I/O 부하가 심한 대규모 서비스를 개발하기 적합하다고 할 수 있다. (또한 자바스크립트의 표준 라이브러리(CommonJS)의 스펙을 따르고 있다)
노드의 탄생 배경
노드는 2009년 라이언 달(Ryan Dahl)이 고안해 낸 언어로 같은 해 JSConf EU 컨퍼런스에서 처음 발표돼었다.
노드가 개발된 배경과 목적은 다수의 연결을 효율적으로 관리하고 비용을 최소화할 수 있는 네트워크 소프트웨어를 개발하기 편리한 방법을 제공하기 위함이었다. 기존의 웹 애플리케이션 개발 방법은 최초에는 CGI같은 펄 등의 스크립트로 개발했었다. 이후에는 ASP, JSP, PHP 등의 웹 개발을 위한 전용 스크립트 언어들이 득세하였다. 특히 PHP의 경우 오픈 소스로서 그 개발의 용이함 때문에 많은 웹 애플리케이션과 웹 서비스들이 PHP로 구현되기도 했다. 노드는 이러한 시대 이후에 등장한 것으로써 자바스크립트를 서버에서 사용하고자 하는 노력이 반영된 결과이다.
가장 널리 쓰이는 JSP나 PHP와 같은 언어로 웹 애플리케이션을 개발한다고 한다면 이 웹 애플리케이션은 일반적으로 아파치와 같은 웹 서버에서 동작하게 될 것이다. 이 때 어떤 클라이언트가 웹 서버에 연결을 요청하게 된다면, 일정한 메모리 공간을 사용하여 새로운 쓰레드를 생성한다. 이런 형태로 웹 애플리케이션을 개발하여 서비스를 제공하는 경우에는 더 많은 사용자를 지원하기 때문에 사업자는 더 많은 서버를 추가할 수 밖에 없었다. 이는 서버 구매비용뿐만 아니라 운영하는 비용이나 운영에 따라 발생하는 트래픽 비용, 인건비 등 여러 비용이 추가적으로 발생하는 문제가 생겼다. 게다가 여러대의 서버를 사용하더라도 사용자 입장에서는 마치 하나의 서버에 접속하는 것과 같은 효과를 주어야 하기 때문에 모든 서버들은 같은 데이터를 동기화해야 한다는 문제를 발생시키기도 한다.
그래서 노드가 이런 문제를 해결하기 위해서 등장했다.
노드는 서버에서 클라이언트로부터의 요청, 즉 연결을 처리하는 방법을 새로운 컨셉으로 변경하여 이 문제를 해결한다. 기존에는 각 연결에 대해 새로운 쓰레드를 생성하고 그에 따라 메모리를 할당하여 사용자 요청을 처리했다면, 노드에서는 각 연결이 하나의 이벤트로서 노드 엔진이 처리된다.
Node.js를 사용하는 서비스들
노드는 다른 언어들 만큼 등장한 지 오래되지 않았음에도, 이를 실제로 적용한 기업들의 사례가 늘어나고 있다.
LinkedIn
유명한 SNS 서비스 중 하나인 LinkedIn은 자사의 모바일 웹 관련 서비스를 위해서 노드를 활용하고 있습니다. LinkedIn의 경우도 노드와 mongoDB를 활용하고 있습니다. 대표적인 사례로 자신들의 메인 서비스를 Ruby On Rails로 돌릴 때는 15대의 서버에서 15개의 인스턴스로 돌리다가, 노드로 개발한 뒤에는 두 배에 달하는 트래픽을 단지 4개의 인스턴스 만으로도 운영이 가능하다고 합니다. 또한 링크드인의 인턴쉽을 수행하던 사람들이 24시간동안의 핵데이HackDay를 통해 노드를 이용하여 VNC 클라이언트를 구현한 재미있는 케이스도 있습니다.
Cloud9IDE
Cloud9IDE는 가장 널리 쓰이고 있는 웹기반 통합개발환경으로서 웹브라우저에서 노드 기반의 애플리케이션을 쉽게 개발할 수 있도록 해주는 서비스입니다. 노드로 웹애플리케이션을 개발할 수 있는 환경을 제공하지만 그 서비스 자체도 노드를 이용하여 개발되어 있습니다. 사용자와 직접 상호 작용하는 프론트엔드 부분을 HTML5/CSS,/Javascript로 제작함과 동시에 서버에서 실질적으로 작업을 수행하는 백엔드에서도 노드를 사용하여 하나의 개발 언어만으로 전체 서비스를 개발하였습니다.
이벤트 기반 비동기 방식
노드가 뛰어난 성능을 발휘할 수 있는 이유는 크게 비동기 이벤트 기반 아키텍쳐와 구글의 V8 자바스크립트 엔진을 이용했기 때문이라고 말할 수 있다. V8 자바스크립트 엔진에 대한 설명은 생략한다.
쓰레드 기반 vs 비동기 이벤트 기반
지금까지 대부분의 애플리케이션들은 Blocking I/O를 사용하였고, 이 때문에 멀티 쓰레드를 사용할 수 밖에 없었다. 멀티 쓰레드는 개발자 입장에서 직관적이고 멀티 태스킹을 위해 어쩔 수 없는 선택이지만, 네트워크에서 동시에 대규모 요청을 동시에 처리하는 데에는 부적절하다. 멀티 쓰레드에 대해서는 아래에서 좀 더 자세히 다뤄보도록 한다.
Blocking I/O
일반적인 어플리케이션들은 대부분 Blocking I/O를 사용한다. Blocking I/O에 대해 자세히 설명하자면, "하나의 프로세스가 어떤 자원을 사용하고자 할 때 그 자원을 다른 프로세스가 점유하고 있다면, 그 프로세스가 그 자원의 사용을 끝마칠때까지 기다려야 한다는 것" 을 의미한다. 운영체제를 공부해봤다면 들어본 적이 있을 것이다.
먼저 애플리케이션이 운영체제의 커널에게 파일을 읽기 위해 시스템 콜이라는 형태로 요청을 보낸다. 커널은 파일을 읽기위한 동작을 수행하기 시작하고 애플리케이션은 커널이 파일을 다 읽을 때까지 기다려야 한다. 일반적으로 이 상태를 애플리케이션이 Blocked 되었다고 표현하며 이 시간 동안 실제로 애플리케이션은 아무것도 하지 않은 상태가 된다.
멀티 쓰레드
일반적으로 하나의 프로세스가 하나의 요청에 대해 대응하고 그 일을 처리하게 되는데, 만약 웹 서버와 같이 다수의 요청이 들어온다면 멀티 쓰레드라는 개념을 사용할 수 밖에 없게 된다.
멀티 쓰레드는 말 그대로 쓰레드가 동시에 여러개 실행되어 요청을 처리한다는 개념이다. 이것은 CPU의 시분할이라는 개념으로 설명될 수 있다. 시분할 개념은 CPU를 여러 프로세스 또는 쓰레드가 시간을 나누어 동작하도록 함으로써 마치 CPU를 공유하여 사용하는 것과 같은 효과를 낸다.
그림의 첫 번째 경우는 시분할을 이용하지 않고 싱글 쓰레드로 요청을 처리할 때의 모습이다. 먼저 들어온 요청을 먼저 처리하고 이 요청에 대한 처리가 끝날 때까지 기다린 후에 다음 요청을 처리하는 식이다. 따라서 먼저 요청한 작업이 먼저 응답 받을 수 있다.
두 번째 경우는 단순한 프로세스 스케줄링을 적용한 경우로 특정 쓰레드가 일정 시간만 CPU를 사용할 수 있도록 되어있다. 이 경우 프로세스 C가 가장 늦게 요청을 했음에도 불구하고 가장 먼저 응답을 받을 수도 있음을 보여주고 있다.
CPU를 사용할 수 있는 시간이 더 적게 설정 된 세 번째 경우에도 마찬가지로 먼저 요청이 온 A보다 B가 먼저 작업을 마치고 응답을 할 수도 있는 모습을 보여주고 있습니다.
쓰레드로 인해 발생하는 문제
멀티 쓰레드를 통해 복수의 요청으로 인해 발생하는 공유 문제는 해결할 수 있었다.
CPU 사용에 스케줄링을 통해 멀티 쓰레딩을 하더라도 하나의 CPU를 다수의 쓰레드가 사용하고자 한다면, 결국 CPU 자체도 하나의 자원이기 때문에 여러 쓰레드들이 CPU를 점유하기 위해 기다릴 수 밖에 없다는 점은 근본적인 문제이므로 제외시키자. 그렇다면 쓰레드 기반으로 동작할 때 발생하는 실질적인 문제점은 무엇일까?
먼저 Blocking I/O 자체가 발생시키는 쓰레드 지연에 대한 문제이다. 더 쉽게 이야기하면 I/O 요청을 하고 응답이 올때까지 아무것도 하지 않고 시간을 낭비하는 문제이다.
다음으로 스케줄링을 위한 처리 시간과 문맥 전환(Context switch) 비용이 발생한다는 점이다. 즉 쓰레드를 분배하여 사용하기 위해 사용되는 스케줄링 그 자체도 CPU를 이용한 연산이 필요한 작업이고, 쓰레드 간의 전환을 위해서는 전환 하기 직전의 쓰레드를 나중에 복귀시킬 때를 대비하여 그 상태를 저장해두어야 하는데, 이 또한 CPU를 이용한 연산이 필요한 작업이다. 따라서 쓰레드가 많아질수록 문맥전환에 따른 성능 저하가 발생할 수 있는데, 이 때문에 쓰레드들은 별도로 관리하거나, 더 작은 단위로 쪼개어 VM 등으로 실제 쓰레드로 분배하는 방식 같은 대안이 등장하고 있다. 이러한 방식들은 문맥 전환에 드는 시간이 네이티브 CPU 쓰레드를 사용하는 것 보다 더 적다.
싱글 쓰레드와 이벤트 기반의 비동기 I/O 처리
노드는 이러한 문제들을 싱글 쓰레드와 이벤트 기반의 I/O 처리로 해결하고 그 성능을 끌어올릴 수 있도록 하는 비동기 프로그래밍 모델을 제공해주고 있다.
싱글 쓰레드가 가진 노드는 I/O 작업이 시작되면 I/O 작업 처리에 대한 응답을 기다리지 않고, 바로 다음 작업을 실행해버린다. 대신 I/O 작업이 종료되면 이벤트를 발생시키고, 이 이벤트는 해당 프로세스의 이벤트 큐에 등록되게 된다. 노드로 개발된 프로세스는 이 이벤트 큐에 등록된 새로운 이벤트를 감지하여, 해당 이벤트 시 수행되어야 할 작업을 실행하게 된다.
이벤트 루프
이벤트 루프(Event Loop)라는 것은 작업을 요청하면서 그 작업이 완료되었을 때 어떤 작업을 진행할 지에 대한 콜백 함수를 지정하여 동작이 완료되었을 때 해당 콜백 함수를 실행되는 방식의 동작 방식을 말한다.
만약 클라이언트가 웹 서버에 HTTP 형식으로 요청을 하게 되면 서버에는 이벤트 루프가 계속 돌고 있다가 이를 감지하고 알맞은 작업을 워커 쓰레드를 생성하여 실행한다. 이 때 이벤트 루프는 해당 워커 쓰레드가 작업을 마친 뒤 그 결과와 함께 응답할 때까지 기다리는 것이 아니라 바로 루프로 복귀하여 다른 요청을 기다리게 된다.
즉 이벤트 루프는 어떤 요청이 발생하면 그 작업에 대해 쓰레드 실행만을 일으킬 뿐이다. 이 후 작업을 할당 받았던 해당 쓰레드가 모든 작업을 마치면 미리 전달받은 콜백 함수를 실행하도록 이벤트 루프로 응답하게 되며 이벤트 루프는 이것을 실행하여 클라이언트에게 결과를 응답해준다.
Node.js의 특징
지금까지 노드의 가장 큰 특징 중 하나인 이벤트 기반의 비동기 처리 방식에 대해 알아보았다. 하지만 이것만으로는 노드에 매력을 알 수 없다. 왜냐하면 노드는 이벤트 기반의 비동기 처리 방식이라는 특징외에도 다양한 매력을 가지고 있는 프로그래밍 언어이다. 특히 자바스크립트를 기반으로 하고 있다는 점과 이벤트 기반의 프로그래밍 모델, 확장성 있는 모듈 구조는 확실히 노드를 더욱 더 매력적으로 만들어주는 요소들이다. 이 특징들에 대해 좀 더 자세히 설명하도록 하겠다.
기존의 웹 애플리케이션에서 프론트 엔드를 구성하는 대부분이 자바스크립트였던 것을 생각하자. 노드를 사용한다는 것은 이제 백엔드도 자바스크립트를 이용하여 개발할 수 있다는 것이다.
다시 말해 프론트 엔드와 백엔드가 자바스크립트라는 하나의 언어로 통일된다는 것이다. 이것은 단순히 개발 언어의 통일만을 의미하지 않는다. 하나의 개발 언어를 사용하게 되면 프론트 엔드와 백엔드가 좀 더 긴밀한 구조로 설계될 수 있고, 같은 프로그래머가 양쪽 모두를 개발할 수 있다. 따라서 개발 단계에서나 유지보수 단계에서 시간, 인력을 줄일 수 있으며 이로 인해 코드 통합이 쉬워지게 된다. JSON과 같은 공통 데이터 포맷도 프론트엔드와 백엔드 사이에서 쉽게 사용될 수 있다.
이벤트 기반의 프로그래밍 모델
이번에는 이벤트 기반의 비동기 처리 방식으로 인해 발생하는 성능 향상에 대한 장점이 아닌 프로그래밍 모델 자체에 대해 언급한다.
이벤트 기반의 프로그래밍 모델이 처음일 수도 있겠지만 사실 근 몇 년 동안 프론트엔드 개발에서는 jQuery라는 자바스크립트 라이브러리가 널리 쓰이고 있었다. 이 jQuery가 이벤트 기반의 비동기 처리 방식으로 개발을 할 수 있도록 해주는데, 이러한 jQuery에 익숙한 개발자들에게는 이벤트 기반의 프로그래밍 모델을 가지고 있는 노드가 매력적으로 느껴질 것이다.
NPM을 통한 다양한 확장 모듈들
노드는 확장성이 뛰어난 모듈 구조를 가지고 있는데, 이를 통해 기본적으로 제공되는 모듈이외에도 다양한 확장 모듈을 설치하여 사용할 수 있다. 게다가 NPM이라는 설치 관리자를 통해 확장 모듈을 쉽게 설치할 수 있도록 되어 있다. NPM을 통한 다양한 기능을 가진 확장 모듈들은 노드의 보급에 큰 영향을 끼쳤으며, 앞으로도 훌륭한 확장 모듈들이 쏟아져 나올 것으로 보인다.
이동욱 님의 '스프링 부트와 AWS로 혼자 구현하는 웹 서비스' 책을 차근차근 따라가면서 웹 프로젝트를 진행하고 있는 중에 <"컨트롤러에서 맵핑된 방식으로 js에서도 매핑이 되어야 한다. (이는 REST 규약에 맞게 설정된 것이다.)"> 괄호안의 REST 규약이라는 단어의 정의를 잘 몰랐는데 웹 과정 수업을 들으면서 언뜻? 언급돼었던 기억이 났다. 생각이 난 김에 REST(?)란 무엇인지 알아보고 가는 시간을 가지기로 했다.
REST API
웹 어플리케이션에서 제공하는 모든 자원을 URI를 할당하여 활용하는 방식
GET, POST, PUT, DELETE 가 대표적이다.
생겨난 이유
하드웨어와 소프트웨어의 발전으로 인해 프레임 워크 / 라이브러리의 발전과 앱이 등장했고, 클라이언트와 서버의 분리로 생겨났습니다. 하나의 어플리케이션에서 클라이언트와 서버를 관리하던 추세에서 앱의 등장이 있었고 확장성과 안정성을 위해 분산 애플리케이션과 API 서버가 일반화되고 있다.
작성 시 지켜야 할 규칙
복수 명사를 사용한다 ex) users
GET(read), POST(create), PUT(update), DELETE(delete) 각각의 기능을 지킨다. ex) GET / user/123? delete : GET을 사용하고 삭제를 요청하면 안 된다.
POST로 arguments를 보내지 않도록 한다 ex) RESTful API 보안은 HTTP header에 인증정보를 포함하여 https로 처리하는 방법 과 ex) Aouth2가 있다
Sub-Resource를 관계 설정을 위해 사용한다 ex) GET /users/123/friends : 유저 123의 친구목록을 보여준다. ex) GET /users/123/friends/1 : 유저 123의 친구 1번을 보여준다
Equals와Hashcode, toString 등.. 기존의 정의되어있는 메소드를 오버라이딩을 하여 사용하는 경우가 많다. Equals와 toString은 어떤 용도로 사용하는지 알고있었지만, Hashcode의 경우 IntelliJ IDE의 자동완성 기능에서 종종 보았지만, 의미와 용도를 잘 모르고 지나치는 경우가 많았다. (부끄럽지만 알아볼 생각도 안했다)
Equals와 Hashcode 메소드에 대해 알아보자 !
아래의 코드와 같이 member1객체와 member2객체를 선언하고 객체를 비교하였을 때, 결과는 어떻게 될까? 답은 당연히 false일 것이다. 그 이유는 둘은 동일 객체가 아니기 때문이다.
public class Test {
public static void main(String[] args) {
final Member member1 = new Member("1");
final Member member2 = new Member("1");
System.out.println(member1 == member2); //false
System.out.println(member1.equals(member2)); //false
}
public static class Member {
private String id;
public Member(final String id) {
this.id = id;
}
}
}
동일 객체란 서로 같은 참조를 바라보는 것을 말한다. 예를 들어 아래와 같을 수 있다.
public class Test {
public static void main(String[] args) {
final Member member1 = new Member("1");
final Member member2 = member1;
System.out.println(member1 == member2); //true
System.out.println(member1.equals(member2)); //true
}
public static class Member {
private String id;
public Member(final String id) {
this.id = id;
}
}
}
위의 예제의 경우 동일 객체이기 때문에, 둘 다 true 값이 나온다. 유저의 id가 같은데 왜 다른 유저가 되는지에 대해 의문을 품을 수 있다. 보통 자바에서 이것을 동등 객체라고 한다. 동등 객체를 비교하기 위해서는 equals 함수를 사용해야 한다. 하지만 이전 예제에서 equals 함수의 결과값도 false가 나왔다. 이는 equals 함수를 재정의 하지 않았기 때문이다.
객체 비교(equals( ))
다음은 Object의 equals() 메소드이다.
public boolean equals(Object obj) {...}
Object 클래스의 equals()는 아래와 같은 기능으로써 이 메소드는 비교 연산자인 ==과 동일한 결과를 리턴한다. 오로지 참조값이 같은지(= 동일 객체인지) 확인하는 기능이다.
public boolean equalse(Object obj) {
return this == obj;
}
자바에서는 두 객체를 동등 비교할 때 equals() 메소드를 흔히 사용한다. equals() 메소드는 두 객체를 비교해서 논리적으로 동등하면 true를 리턴하고 그렇지 않으면 false를 리턴한다. 논리적으로 동등하다는것은 둘의 참조값과 관계없이 객체 내부의 값이 같다는 것을 의미한다. 이 equals 함수를 재정의한 대표적인 예가 String 클래스이다. String 클래스의 equals 함수는 번지수 비교가 아닌, 문자열을 비교한다. 아래는 String Class의 equals메소드의 코드이다.
//String class
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
위 소스를 보면 Object로 객체를 받아 하나하나 문자열을 비교하는 것을 볼 수 있다. char은 참조값이 아닌 기본값이기 때무에 == 로 비교가 가능하다.
팁: String을 이용해 문자열 리터럴방식으로 문자열을 정의하면 동일 객체를 생성하게 되어있다.
예를 들어 Member라는 클래스가 있는데 여기에 Id값이 같은경우에 동등객체로 취급한다면 equals() 메소드를 재정의해서 id필드값이 같음을 비교하면 된다. 아래의 코드를 참조하자.
public class Member {
public String id;
public Member(String id) {
this.id = id;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Member) {
Member member = (Member) obj;
if (obj.equals(id)) {
return true;
}
}
return false;
}
}
위와 같이 equals 메소드를 재정의하게 되면 Member 객체를 통해 객체를 비교할 때 id가 같다면 두 객체는 동등할 것이다.
동일성비교는 == 이다. 객체 인스턴스 의 주소 값을 비교한다.
동등성 비교는 equals() 메소드를 사용해서 객체 내부의 값을 비교한다.
Java HashCode란?
객체 해시코드란 객체를 식별한 하나의 정수값을 말한다. Object의 hashCode() 메소드는 객체의 메모리 번지를 이용해서 해시코드를 만들어 리턴하기 때문에 객체 마다 다른 값을 가지고있다. 객체의 값을 동등성 비교시 hashCode()를 오버라이딩 할 필요성이 있는데, 컬렉션 프레임워크에서 HashSet, HashMap, HashTable은 다음과 같은 방법으로 두 객체가 동등한지 비교한다.
우선 hashCode() 메소드를 실행해서 리턴된 해시코드 값이 같은지를 본다. 해시 코드값이 같으면 equals() 메소드로 다시 비교한다. 이 두개가 모두 맞아야 동등 객체로 판단한다.
아래의 예제를 보면 Key 클래스의 equals 메소드를 재정의해서 number 필드값이 같으면 true를 리턴하도록했다. 그러나 hashCode 메소드를 재정의 하지 않았기 때문에 Object hashCode() 메소드가 사용된다.
public class Key {
public int number;
public Key(int number) {
this.number = number;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Key) {
Key compareKey = (Key) obj;
if (this.number == compareKey.number) {
return true;
}
}
return false;
}
}
이런 경우 두개의 동등한 객체를 HashMap의 식별키로 Key 객체를 사용하면 저장된 값을 찾아 오지 못한다. 왜냐하면 number 필드 값이 같더라도 hashCode() 메소드에서 리턴하는 해시코드가 다르기 때문에 다른 식별키로 인식하기 때문이다. 아래의 예제를 실행해보자!
public class KeyExample{
public static void main(String[] args){
//Key 객체를 식별키로 사용해서 String 값을 저장하는 HashMap 객체생성.
HashMap<Key, String> hashMap = new HashMap<Key, String>();
//식별키 "new key(1)"로 "홍길동"을 저장함
hashMap.put(new Key(1), "홍길동");
//식별키 "new key(1)"로 "홍길동"을 읽어옴
String value = hashMap.get(new Key(1));
System.out.println(value);
}
}
위에 내용에서 Hash Code를 정의하지 않았기 때문에 값은 null이 조회될 것이다. 만약 의도한 대로 홍길동을 읽으려면 아래와 같이 hashCode를 재정의 해야 한다.
public class Key{
...
@override
public int hashCode(Object obj){
return number;
}
}
저장할 때의 new Key(1)과 읽을 때의 new Key(1)은 다른 참조값의 다른 객체이지만 HashMap은 hashCode()의 리턴값이 같고, equals() 리턴값이 true가 나오기 때문에 두 객체는 동등 객체로 평가하여 하나의 키처럼 사용하게 된다. (즉 같은 식별키로 인식한다는 뜻이다) 이러한 이유로 객체의 동등성 비교를 위해서는 Object의 equals() 메소드만 재정의하지 말고, hashCode() 메소드도 재정의해서 논리적 동등 객체일경우 동일한 해시코드가 리턴되도록 해야한다.
다시한번 강조하지만 키를 기반으로 찾는 것이지 객체의 참조 값으로 찾는 것은 아니다. 고로 hash code값이 같다고해서 객체의 참조 값이 같은 것은 아니다.
해시코드 생성(hash(), hashCode())
object 중에 해쉬코드를 생성해주는 역할이 있다. 이 Object.hash(Object ...values) 메소드는 매개값으로 주어진 값들을 이용해서 해시 값들을 이용해서 해시 코드를 생성하는 역할을 하는데, 주어진 매개값들로 배열을 생성하고 Arrays.hashCode() 재정의할 때 리턴값을 생성하기 위해 사용하면 좋다. 클래스 하나가 여러가지 필드를 가지고 있을 때 이 필드들로부터 해시코드를 생성하게 되면 동일한 필드값을 가지는 객체는 동일한 해시코드를 가질 수 있다. 좀 더 정밀한 동등 객체를 이용한다면 이 방법을 사용할 수 있다.
IDE 또는 lombok을 이용해 더 쉽게 equals, hashCode를 재정의 할 수 있다.
@Override
public int hashCode() {
return Objects.hash(field1, field2, filed3);
}
기대하고 갈망했던 '우아한 테크 코스 2기'에 지원했다가 보기 좋게 떨어져 버렸다. 허탈함에 한동안 유튜브만 보면서 시간을 보냈다. 대책없이 이렇게 살면 안되겠다는 생각이 드는 찰나 지인(은인)으로부터 '생활코딩' 커뮤니티에 올라온 스터디 모집공고글을 캡쳐해서 보내줬다. 내용을 보아하니 스터디를 모집하시는 분도 우테코를 지원한 나의 동지였다. 왠지모를 동질감과 좌절하지 않고 바로 행동으로 옮기는 추진력을 보면서 나 자신이 약간 한심하게 느껴졌다. 그리고 스터디에 참가하고 싶다는 메일을 바로 작성했다. 그렇게 모집된 10명의 멤버들은 우테코 없는 우테코(=홍철 없는 홍철팀)를 목표로 우테코 1기 수강생분의 도움을 받아 일단 한달간(3주 or 4주) 우테코 1기 커리큘럼을 따라 프리코스 기간을 가지기로 했다.
1주차 미션은 '숫자야구게임' 이다. 숫자야구게임의 Rule을 그대로 적용한 프로그램을 만드는 것이다. 하지만 평소처럼 단순하게 코드를 짜는 것이었다면 왜 프리코스라는 과정이 있겠는가? 그렇다 쉽지않은 제약조건이 있었다.
소프트웨어의 유지보수를 그 소프트웨어를 직접 개발한 개발자가 담당하는 경우는 거의 보기 힘들다.
코딩 규칙을 지키면 다른 개발자가 그 소스 코드를 처음 보았을 때, 더 빠른 시간 안에 완벽하게 이해할 수 있도록 도와주기 때문에, 소프트웨어의 가독성이 높아진다.
개발자가 자신의 소스 코드를 제품으로 팔려고 한다면, 자신이 작성한 다른 소스 코드들과 잘 어울리도록패키지(package)를 적절하게 구성할 필요가 있다.
2. 파일 이름
흔하게 사용되는 파일 확장자와 파일 이름을 설명한다.
2-1. 파일 확장자
자바 소프트웨어는 다음의 파일 확장자를 사용한다.
파일형태
확장자
자바 소스
.java
자바 바이트코드
.class
2-2. 공통으로 사용되는 파일 이름
공통으로 다음과 같은 파일 이름을 사용한다.
파일 이름
사용
GNUmakefile
make파일 이름으로 사용. 소프트 웨어를 빌드할 때는 gnumake 명령어를 사용.
README
특정 디렉토리의 내용을 요약하는 파일 이름으로 사용.
3. 파일 구조
파일은 빈 줄이나 다른 구역임을 나타내는 주석으로 나누어지는 여러 구역(section)들로 구성되어 있다. 2000 라인을 넘는 파일은 이해하기가 쉽지 않기 때문에 될 수 있으면 피해야 한다. 적절하게 구성된 자바 프로그램의 예제는,"11.1. 자바 소스 파일 예제"에서 볼 수 있다.
3-1. 자바 소스 파일
각각의 자바 소스 파일은 하나의 public 클래스 또는 인터페이스를 가진다. Private 클래스들과 인터페이스들이 public클래스와 연결되어 있을 때, public 클래스와 같은 파일에 private 클래스들과 인터페이스들을 넣을 수 있다. Public 클래스는 파일에서 첫번째 클래스 또는 인터페이스이어야 한다.
자바 소스 파일은 다음과 같은 순서를 가진다.
시작 주석
Package문과 Import문
클래스와 인터페이스
3-1-1. 시작 주석
모든 소스 파일은 클래스 이름, 버전 정보, 날짜, 저작권 주의를 보여주는 C 스타일의 주석과 함께 시작하라.
Source Code
/*
* 클래스 이름
*
* 버전 정보
*
* 날짜
*
* 저작권 주의
*/
3-1-2. Package 및과 Import 문
대부분의 자바 소스 파일에서 주석이 아닌 첫번째 줄은 package 문이다. 그 후에, import문이 뒤따라 나온다. 예를 들면 다음과 같다.
주의: Package는 한 번만 선언되어야 하고, pacakage 이름의 첫번째 부분은 모두 소문자 ASCII 문자를 사용해야 하고, 첫번째 레벨의 도메인 이름들(com, edu, gov, mil, net, org)중에 하나이거나 1981년 ISO Standard 3166에서 정의된 영어 두 문자로 표현되는 나라 구별 코드 중에 하나이어야 한다.
3-1-3. Class와 Interface 선언
다음 표는 클래스또는 인터페이스 선언의 일부분들을 나타나는 순서에 따라 보여준다.
클래스 / 인터페이스 선언의 구성요소
설명
1
클래스/인터페이스 문서화 주석 ( /**...*/ )
이 주석이 포함하는 정보들은 "5.2. 문서화(Documentation) 주석"을 참고하자.
2
클래스 / 인터페이스 문
3
필요할 경우, 클래스 / 인터페이스구현 주석 ( /*...*/ )
이 주석은 클래스 / 인터페이스 문서화 주석에 적합하지 않은 하나의 클래스 / 인터페이스에만 해당하는 정보들을 포함해야 한다.
4
클래스(static) 변수
첫번째로는 public 클래스 변수들이 나오고, 그 다음에 protected 클래스 변수들, 그 다음에 package(접근 제한자(access modifier)가 없는 경우) 클래스 변수들, 그 다음에 private 클래스 변수들이 나온다.
5
일반 변수
작성하는 순서는 클래스 변수와 동일하다.
6
생성자
7
메서드
메서드는 범위나 접근성을 기준으로 나누기 보다는 기능성에 의해서 구성되어야 한다. 예를 들어, private 클래스 메서드가 두 개의 public 메서드들 사이에 존재할 수 있다. 이렇게 하는 목적은 코드가 더 쉽게 읽히고, 더 쉽게 이해되도록 하기 위해서이다.
4. 들여쓰기
4개의 빈 칸(space)을 들여쓰기 단위로 사용한다. 들여쓰기의 정확한 구현(빈 칸을 사용하거나 탭을 사용하거나)은 정해져 있지 않다. 탭은 4개의 빈 칸이 아니라, 8개의 빈 칸으로 설정하는 것이 좋다.
4-1. 한 줄의 길이
한 줄에 80자 이상 쓰는 것은 대부분의 터미널(terminal)과 툴에서 다룰 수 없기 때문에 피해야 한다. 주의: 문서화 주석을 가지고 문서를 만들 때, 일반적으로 한 줄에 70자 이상을 가지지 않는다.
4-2. 줄 나누기
하나의 식이 한 줄에 들어가지 않을 때에는, 다음과 같은 일반적인 원칙을 따라서 두 줄로 나눈다.
콤마 후에 두 줄로 나눈다.
연산자(operator) 앞에서 두 줄로 나눈다.
레벨이 낮은 원칙 보다는레벨이 높은 원칙에 따라 두 줄로 나눈다.
앞줄과 같은 레벨의 식(expression)이 시작되는 새로운 줄은 앞줄과 들여쓰기를 일치시킨다.
만약 위의 원칙들이 코드를 더 복잡하게 하거나 오른쪽 끝을 넘어간다면, 대신에 8개의 빈 칸을 사용해 들여쓴다.
여기 메서드 호출을 두 줄로 나누어 쓰는 몇 가지 예제들이 있다.
Source Code
someMethod(longExpression1, longExpression2, longExpression3,
longExpression4, longExpression5);
var = someMethod(longExpression1,
someMethod(longExpression2,
someMethod(longExpression3));
다음은 수학 표현식을 두 줄로 나누어 작성하는 두 개의 예제이다. 첫번째 예제가 괄호로 싸여진 표현식 밖에서 줄 바꿈이 일어나고 더 높은 레벨이기 때문에 첫번째 예제를 더 많이 사용한다.
Source Code
longName1 = longName2 * (longName3 + longName4 - longName5)
+ 4 * longName6; // 될 수 있으면 이 방법을 사용한다.
longName1 = longName2 * (longName3 + longName4
- longName5) + 4 * longname6; // 될 수 있으면 피한다!
다음은 메소드 선언을 들여쓰는 예제들이다. 첫번째는 일반적인 경우이다. 두 번째 예제의 경우 일반적인 들여쓰기를 사용한다면 두 번째 줄과 세 번째 줄을 아주 멀리 들여써야 하므로, 대신에 8개의 빈 칸을 이용하여 들여썼다.
Source Code
// 일반적인 들여쓰기
someMethod(int anArg, Object anotherArg, String yetAnotherArg,
Object andStillAnother) {
...
}
// 너무 멀리 들여쓰는 것을 피하기 위해 8개의 빈 칸으로 들여쓰기
private static synchronized horkingLongMethodName(int anArg,
Object anotherArg, String yetAnotherArg,
Object andStillAnother) {
...
}
일반적으로 메서드 본문이 시작할 때 4개의 빈 칸을 사용하므로, 메서드 본문과 구분하기 위해서 줄을 나누는 경우의 들여쓰기는 일반적으로 8개의 빈 칸 원칙을 사용하자. 예를 들어 :
// 아래와 같은 들여쓰기는 사용하지 말자.
if ((condition1 && condition2)
|| (condition3 && condition4)
||!(condition5 && condition6)) { // 좋지 않은 들여쓰기
doSomethingAboutIt(); // 메서드 본문 시작이 명확하지가 않다.
}
// 대신 아래와 같은 들여쓰기를 사용한다.
if ((condition1 && condition2),
|| (condition3 && condition4)
||!(condition5 && condition6)) {
doSomethingAboutIt();
}
// 또는 아래와 같은 들여쓰기를 사용한다.
if ((condition1 && condition2) || (condition3 && condition4)
||!(condition5 && condition6)){
doSomethingAboutIt();
}
자바 프로그램은 두 가지 종류의 주석을 가진다. 1) 구현 주석과 2) 문서화 주석으로 나뉘는데, 구현 주석은 /*...*/과 // 에 의해서 경계가 결정되는 C++에서의 주석과 동일하다. 문서화 주석은 단지 자바에서만 사용되며, /**...*/에 의해서 경계가 결정된다. 자바 소프트웨어에 포함되어 있는 javadoc 툴을 사용하면 문서화 주석을 포함하는 HTML 파일을 자동으로 만들 수 있다.
구현 주석은 각각의 구현에 대한 추가적인 설명이 필요할 때, 코드를 주석 처리할 때 사용할 수 있다. 문서화 주석은 소스 코드가 없는 개발자들도 읽고 이해할 수 있도록, 실제 구현된 코드와는 상관이 없는 코드의 명세 사항(specification)을 포함한다.
주석은 코드에 대한 개요와 코드 자체만 가지고는 알 수 없는 추가적인 정보들을 제공하기 위해 사용되어야 한다. 주석은 프로그램을 읽는 것과 이해하는 것에 관계된 정보만을 가지고 있어야 한다. 예를 들어, '패키지를 어떻게 만들것인가?' 또는 '파일들을 어느 디렉토리에 위치시킬것인가?'에 대한 정보는 주석에 포함되어서는 안 된다.
중요하거나 코드만으로는 명확하지 않은 프로그램 설계에 대한 추가 설명을 포함하는 것은 적절하지만, 그러나 코드 상에 이미 표현되어 있는 중복 정보는 피해야 한다. 중복된 주석을 작성하는데 많은 시간을 허비해서 계획된 시간안에 프로그램을 완성하지 못하는 경우도 발생한다. 일반적으로 코드가 계속 발전하면 더 이상은 유효하지 않을 것 같은 정보들은 주석에서 제외하는 것이 더 나은 방법이다.
주의: 때로는 코드에 대한 주석이 많이 필요하다는 것은 코드의 품질이 좋지 않다는 것을 의미한다. 주석을 추가해야 한다고 느낄때, 코드를 좀 더 명확하게 다시 작성하는 것을 고려해 보는 것이 좋다.
주석을 별표(*) 또는 다른 문자를 사용하여 그린 큰 사각형에 넣는 방법은 피하도록 하자.
주석은 폼 피드(form-feed: 프린터에서 용지를 바꿈)나 백스페이스(backspace)와 같은 특수 문자를 포함해서는 안 된다.
5-1. 구현 주석 형식
프로그램은 다음과 같은 4가지 형식의 구현 주석을 포함할 수 있다: 블록(block) 주석, 한 줄(single-line) 주석, 꼬리(trailing) 주석, 그리고 줄 끝(end-of-line) 주석.
5-1-1. 블록(block) 주석
블록 주석은 파일, 메소드, 자료 구조, 알고리즘에 대한 설명을 제공할 때 사용된다. 블록 주석은 각각의 파일이 시작될 때와 메소드전에 사용된다. 또한 메소드 안에서와 같이 다른 장소에서 사용될 수도 있다. 메소드 안에 존재하는 블록 주석은 주석이 설명하는 코드와 같은 단위로 들여쓰기를 해야 한다.
블록 주석은 다른 코드들과 구분하기 위해서 처음 한 줄은 비우고 사용한다.
Source Code
/*
* 여기에 블록 주석을 작성한다.
*/
블록 주석의 들여쓰기를 다시 고치지 못하도록 하기 위한 특별한 블록 주석은 /*- 으로 시작할 수 있다. 예를들어:
Source Code
/*-
* 여기에 들여쓰기를 무시하는 특별한
* 블록 주석을 작성한다.
*
* one
* two
* three
*/
5-1-2. 한 줄(Single-Line) 주석
짧은 주석은 뒤따라 오는 코드와 같은 동일한 들여쓰기를 하는 한 줄로 작성할 수 있다. 만약 주석이 한 줄에 다 들어가는 않는다면, 블록 주석 형식을 따라야 한다. ("5.1.1 블록(block) 주석"을 참고하자") 한 줄 주석은 빈 줄로 시작되어야 한다. 다음은 자바 코드에서 한 줄 주석의 예제이다:
Source Code
if (condition) {
/* 이 조건을 만족하면 실행된다. */
...
}
5-1-3. 꼬리(Trailing) 주석
아주 짧은 주석이 필요한 경우 주석이 설명하는 코드와 같은 줄에 작성한다. 하지만 실제 코드와는 구분될 수 있도록 충분히 멀리 떨어뜨려야 한다.
다음은 자바 코드에서 꼬리 주석을 사용하는 예제이다:
Source Code
if (a == 2) {
return TRUE; /* 특별한 경우 */
} else {
return isPrime(a); /* a 가 홀수인 경우 */
}
5-1-4. 줄 끝(End-Of-Line) 주석
주석 기호 // 는 한 줄 모두를 주석 처리하거나 한 줄의 일부분을 주석 처리해야 할 때 사용할 수 있다. 긴 내용을 주석에 포함하기 위해서 연속되는 여러 줄에 이 주석을 사용하는 것은 안되지만, 어떤 코드의 일부분을 주석 처리하기 위해서 여러 줄에 연속해서 사용하는 것은 허락된다. 다음은 줄 끝 주석을 사용하는 세가지 스타일을 모두 포함하고 있는 예제이다 :
Source Code
if (foo > 1) {
// double-flip을 실행한다.
...
}
else {
return false; // 이유를 여기에 설명한다.
}
//if (bar > 1) {
//
// // double-flip을 실행한다.
// ...
//}
//else {
// return false;
//}
5.2. 문서화(Documentation) 주석
주의: 여기에 나오는 주석들의 예제는 "11.1. 자바 소스 파일 예제"을 참고한다.
문서화 주석은 자바 클래스, 인터페이스, 생성자, 메소드 그리고 필드들을 설명한다. 각각의 문서화 주석은 주석 경계 기호인 /**...*/ 안으로 들어간다. 그리고, 각각의 문서화 주석은 클래스, 인터페이스 그리고 멤버 당 하나씩 가진다. 문서화 주석은 선언 바로 전에 나와야 한다. 다음은 예제이다:
Source Code
/**
* Example 클래스는 ...
*/
public class Exampple { ...
처음 나오는 클래스와 인터페이스들은 들여 쓰지 않는 반면에 그들의 멤버들은 들여쓰기를 한다. 클래스에 대한 문서화 주석(/**)의 첫 번째줄은 들여 쓰지 않는다. 그 다음에 나오는 문서화 주석은 별표를 수직으로 맞추기 위해 각각 1개의 빈 칸을 들여쓰기를 한다. 생성자를 포함한 멤버들은 문서화 주석 첫 줄에서는 4개의 빈 칸을 들여쓰기 하고, 그 이후에는 5개의 빈 칸으로 들여쓰기를 한다.
만약 클래스, 인터페이스, 변수 또는 메소드에 대한 어떤 정보를 제공하고 싶지만 문서화 주석에 어울리지 않는다고 생각된다면, 클래서 선언 바로 후에 블록 주석("5.1.1 블록(block) 주석") 또는 한 줄 주석("5.1.2 한줄(Single-Line) 주석")을 사용한다. 예를들어, 클래스의 구현에 대한 세부 사항들은 클래스에 대한 문서화 주석이 아니라, 클래스 선언문 다음에 블록 주석을 사용해야 한다.
자바는 문서화 주석을 주석 이후에 처음 나오는 선언문과 연결시키기 때문에 문서화 주석은 메소드 또는 생성자 구현 안에 위치해서는 안된다.
6. 선언
6-1. 한 줄당 선언문의 수
한 줄에 하나의 선언문을 쓰는 것이 주석문 쓰는 것을 쉽게 해주기 때문에 한 줄에 하나의 선언문을 쓰는 것이 좋다. 다시 말해서,
Source Code
int level; // 들여쓰기 단위
int size; // 테이클 크기
위와 같이 쓰는 것이 아래와 같이 쓰는 것보다 좋다.
Source Code
int level, size;
같은 줄에 서로 다른 타입을 선언하면 안 된다. 예를 들어:
Source Code
int foo, fooarray[]; // 절대 이렇게 사용하지 말자!
주의: 위의 예제는 타입과 변수 이름사이에 하나의 space를 두었다. 또 다른 사용 가능한 방법은 탭을 사용하는 것이다. 예를 들어 다음과 같은 방법도 가능하다.
Source Code
int lever; // 들여쓰기 단위
int size; // 테이블 크기
Object currentEntry; // 테이블에서 현재 선택된 데이터
6-2. 초기화
지역 변수의 경우, 지역 변수를 선언할 때 초기화 하는 것이 좋다. 변수의 초기화 값이 다른 계산에 의해서 결정되는 경우라면, 변수를 선언할 때 초기화 하지 않아도 괜찮다.
6-3. 배치
선언은 블록의 시작에 위치해야 한다. (보통 블록은 중괄호인 "{" 과 "}"로 둘러싸인 코드를 이야기한다.) 변수를 처음 사용할 때까지 변수의 선언을 미루지 말아라. 이러한 경우 부주의한 프로그래머들을 혼란에 빠뜨릴 수 있고, 영역내에서 코드를 이동해야 하는 경우에 문제를 야기시킬 수 있다.
Source Code
void myMethod() {
int int1 = 0; // 메소드 블록의 시작
if (condition) {
int int2 = 0; // "if" 블록의 시작
...
}
}
이러한 원칙에도 예외가 존재한다. 그 중 하나가 for 문에서 선언하는 반복문을 위한 인덱스이다:
Source Code
for (int i = 0; i < maxLoops; i++) { ... }
상위 영역에 선언된 것을 숨기기 위해서 블록의 처음 부분에서 다시 선언하는 것은 피해야 한다. 예를 들어, 블록 안의 블록에서 동일한 변수 이름을 사용해서 선언하지 말아야 한다.
Source Code
int count;
...
myMeyhod() {
if (condition) {
int count = 0; // 이렇게 사용하지 말 것!
...
}
...
}
6.4. 클래스와 인터페이스 선언
자바 클래스와 인터페이스를 선언할 때, 다음과 같은 원칙을 따르도록 하자:
메서드 이름과 그 메서드의 파라미터 리스트의 시작인 괄호 "(" 사이에는 빈 공간이 없어야 한다.
여는 중괄호 "{"는 클래스 / 인터페이스 / 메소드 선언과 동일한 줄의 끝에 사용하자.
닫는 중괄호 "}"는 "}"가 여는 중괄호 "{" 후에 바로 나와야하는 null문장을 제외하고는, 여는 문장과 동일한 들여쓰기를 하는 새로운 줄에서 사용하자.
Source Code
class Sample extends Object {
int ivar1;
int ivar2;
Sample(int i, int j) {
ivar1 = i;
ivar2 = j;
}
int emptyMethod() {}
...
}
메소드들의 구분하기 위해서 각 메소드들 사이에는 한 줄을 비우도록 하자.
7. 문 (Statement)
7-1. 간단한 문
각각의 줄에는 최대한 하나의 문(Statement)만 사용하도록한다. 예를 들어:
Source Code
argv++; // 올바른 사용법
argc--; // 올바른 사용법
argv++; argc--; // 이렇게 작성하는 것은 피해라!
7-2. 복합문
복합문은 중괄호 "{ statements }"로 둘러싸여진 문들의 리스트를 포함하는 문이다. 이 리스트에 포함될 수 있는 문들은 다음 절에서부터 하나 하나 예를 들어 설명하겠다.
둘러싸여진 문들은 복합문보다 한 단계 더 들여쓰기를 한다.
여는 중괄호("{")는 복합문을 시작하는 줄의 마지막에 위치해야 한다. 닫는 중괄호("}")는 새로운 줄에 써야 하고, 복합문의 시작과 같은 들여쓰기를 한다.
중괄호들이 if-else 문이나 for문 같은 제어 구조의 일부분으로 사용되어질 때에는 이러한 중괄호들이 모든 문들(단 하나의 문일 경우에도)을 둘러싸는데 사용되어져야 한다. 이렇게 사용하는 것이 중괄호를 닫는 것을 잊어버리는 것 때문에 발생하는 버그를 만들지 않고, 문을 추가하는 것에 도움을 준다.
7-3. return 문
값을 반환하는 return 문은 특별한 방법으로 더 확실한 return 값을 표현하는 경우를 제외하고는 괄호를 사용하지 않는 것이 좋다. 예를 들면 다음과 같다:
if (condition) {
statements;
}
if (condition) {
statements;
} else {
statements;
}
if (condition) {
statements;
} else if (condition) {
statements;
} else {
statements;
}
주의: if 문은 항상 중괄호를 사용한다. 다음과 같은 에러가 발생할 수 있는 상황은 피해야 한다 :
Source Code
if (condition) // 이렇게 중괄호 { }를 생략해서 사용하지 말자!
statements;
7-5. for 문
for 문은 다음과 같이 사용하자 :
Source Code
for (initialization; condition; updata) {
statements;
}
빈 for 문(모든 작업이 initialization, condition, update 에서 완료되는)은 다음과 같은 형태를 가져야 한다 :
for (initialization; condition; update);
for 문의 initialization 또는 update 부분에서 콤마(,) 연산자를 사용할 때에는, 세 개 이상의 변수들을 사용하는 복잡성은 피해야 한다. 만약 필요하다면, for 문 이전에 문을 분리시켜 사용(initialization절의 경우)하거나 for 문의 마지막에 문을 분리시켜 사용(update 절의 경우)한다.
7-6. while 문
while 문은 다음과 같이 사용한다 :
Source Code
while (condition) {
statements;
}
빈 while 문은 다음과 같이 사용한다 :
Source Code
while (condition);
7-7. do-while 문
do-while 문은 다음과 같이 사용한다 :
Source Code
do {
statements;
} while (condition);
7-8. switch 문
switch 문은 다음과 같이 사용한다 :
Source Code
switch (condition) {
case ABC:
statements;
/* 다음줄로 계속 진행한다 */
case DEF:
statements;
break;
case GHI:
statements;
break;
default:
statements;
break;
}
모든 case를 수행해야 하는 경우 break문을 수행하지 않으면 된다. 이러한 경우는 위의 예제 코드의 첫번째 case에서 볼 수 있다.
모든 switch 문은 default case를 포함해야 한다. 위의 예제와 같이, 어떤 경우에 default case에서 break는 중복적이지만, 이후에 또 다른 case가 추가되어질 경우 에러를 방지할 수 있다.
명명 규칙, 즉 이름을 정하는 규칙은 프로그램을 더 읽기 쉽게 만들어 줌으로써 더 이해하기 쉽게 만들어 준다. 또한 식별자(identifier)를 통해서 기능에 대한 정보도 얻을 수 있다. - 예를 들어, 그것이 상수인지 패키지인지 클래스인지를 알 수 있도록 도와준다. 이러한 정보는 코드를 이해하는데 큰 도움이 된다.
식별자 타입
명명 규칙
예제
Packages
패키지 이름의 최상위 레벨은 항상 ASCII 문자에 포함되어 있는 소문자로 쓰고, 가장 높은 레벨의 도메인 이름 중 하나이어야 한다. 현재는 com, edu, gov, mil, net, org, 또는 1981년 ISO Standard 316에 명시된 영어 두 문자로 표현되는 나라 구별 코드가 사용된다.패키지 이름의 나머지 부분은 조직 내부의 명명 규칙을 따르면 된다. 이러한 규칙을 따라 만들어진 이름은 디렉토리 구조에서 디렉토리 이름으로도 사용된다. 예를 들어 부서명, 팀명, 프로젝트명, 컴퓨터 이름, 또는 로그인 이름 등이다.
클래스 이름은 명사이어야 하며, 복합 단어일 경우 각 단어의 첫 글자는 대문자이어야 한다. 클래스 이름은 간단하고 명시적이 되도록 작성해라. 완전한 단어를 사용하고 두 문자어와 약어는 피하도록 하자 (만약, 약어가 URL이나 HTML과 같이 더 많이, 더 넓게 사용되고 있다면 약어를 사용하는 것도 괜찮다).
class Raster; class ImageSprite;
Interfaces
인터페이스 이름도 클래스 이름과 같은 대문자 사용 규칙을 적용해야 한다.
interface RasterDelegate; interface Storing;
Methods
메서드의 이름은 동사이어야 하며, 복합 단어일 경우 첫 단어는 소문자로 시작하고 그 이후에 나오는 단어의 첫 문자는 대문자로 사용해야 한다.
run(); runFast(); getBackground();
Variables
변수 이름의 첫 번째 문자는 소문자로 시작하고, 각각의 내부 단어의 첫 번째 문자는 대문자로 시작해야 한다. 변수 이름이 언더바(_) 또는 달러 표시 문자로 시작하는 것이 허용되기는 하지만, 이 문자들로 시작하지 않도록 주의하자.변수 이름은 짧지만 의미 있어야 한다. 변수 이름의 선택은 그 변수의 사용 의도를 알아낼 수 있도록 의미적이어야 한다. 한 문자로만 이루어진 변수 이름은 임시적으로만 사용하고 버릴 변수일 경우를 제외하고는 피해야 한다. 보통의 임시 변수들의 이름은 integer일 경우에는 i, j, k, m, n을 사용하고, character일 경우에는 c, d, e를 사용한다.
Int i; char c; float myWidth;
Constants
클래스 상수로 선언된 변수들과 ANSI 상수들의 이름은 모두 대문자로 쓰고 각각의 단어는 언더바(“_”)로 분리 해야 한다 (디버깅을 쉽게 하기 위해서 ANSI 상수들의 사용은 자제하도록 하자.).
static final int MIN_WIDTH = 4; static final int MAX_WIDTH = 999; static final int GET_THE_CPU = 1;
10. 좋은 프로그래밍 습관
10-1. 인스턴스 변수와 클래스 변수를 외부에 노출하지 말고 대신 접근을 제공!
인스턴스 변수 또는 클래스 변수를 합당한 이유없이 public으로 선언하지 말아라. 인스턴스 변수들은 명시적으로 선언될 필요가 없는 경우도 많다.
인스턴스 변수가 public으로 선언되는 것이 적절한 경우는 클래스 자체가 어떤 동작(behavior)을 가지지 않는 데이터 구조(data structure)일 경우이다. 다시 말해서 class 대신 struct를 사용해야 하는 경우라면 (만약 Java가 struct를 지원한다면), class의 인스턴스 변수들을 pulbic으로 선언하는 것이 적합하다.
10-2. 클래스 변수와 클래스 메소드는 클래스 이름을 사용하여 호출!
클래스(static) 변수 또는 클래스 메소드를 호출하기 위해서 객체를 사용하는 것을 피해야 한다. 대신에 클래스 이름을 사용해라. 예를 들어:
Source Code
classMethod(); // 좋은 사용법
AClass.classMethod(); // 좋은 사용법
anObject.classMethod(); // 나쁜 사용법
10-3. 숫자는 바로 사용하지 말고 선언해서 변수 이름으로 접근!
숫자 상수는 카운트 값으로 for 루프에 나타나는 -1, 0, 1을 제외하고는, 숫자 자체를 코드에 사용하지 말아라.
10-4. 변수에 값을 할당할 때 주의할 것들!
하나의 문(statement)에서 같은 값을 여러 개의 변수들에 할당하지 말아라. 이렇게 하면 읽기가 어렵게 된다. 예를 들어:
Source Code
fooBar.fChar = barFoo.lchar = 'c'; // 이렇게 사용하지 말자!
비교 연산자(equality operator: ==)와 혼동되기 쉬운 곳에 할당 연산자(assignment operator: =)를 사용하지 말아라. 예를 들어:
Source Code
if (c++ = d++) { // 이렇게 사용하지 말자! (자바가 허용하지 않음)
...
}
다음과 같이 작성하자
Source Code
if ((c++ = d++)) != 0) {
...
}
실행시 성능 향상을 위해서 할당문(assignment statement)안에 또 다른 할당문을 사용하지 말아라. 예를 들어:
Source Code
d = (a = b + c) + r; // 이렇게 사용하지 말자!
다음과 같이 써야 한다.
Source Code
a = b + c;
d + a + r;
10-5. 그 외 신경써야 할 것들
10-5-1. 괄호
연산자 우선순위 문제를 피하기 위해서 복합 연산자를 포함하는 경우에는 자유롭게 괄호를 사용하는 것이 좋은 생각이다. 나는 연산자 우선 순위를 확실하게 알고 있다고 할지라도, 다른 프로그래머에게는 생소할 수 있다는 것을 기억하자.
Source Code
if (a == b && c == d) // 이렇게 사용하지 말자!
if ((a == b) && (c == d)) // 이렇게 사용하자!
삼항 연산자(ternary operator - ?:) 에서 ? 이전에 이항 연산자(binary operator)를 포함하는 식 (expression)이 있는 경우에는, 꼭 괄호를 사용해야 한다. 예를 들어:
Source Code
(x > 0) ? x : -x;
11. 코드 예제
11-1. 자바 소스 파일 예제
다음 예제는 하나의 public class를 가지는 자바 소스 파일을 어떻게 구성하는지 보여준다. 자바에서 사용하는 interface도 비슷하게 구성된다. 더 자세한 정보는 "3.1.3. Class와 Interface 선언"과 "5.2. 문서화(Documentation) 주석"을 참고하자.
/*
* @(#)CodeConvention.java 0.82 2000/1/17
*
* [저작권 및 라이센스 관련 정보를 여기에 작성한다.]
* Copyright (c) 2015 Kwangshin Oh.
* ComputerScience, ProgrammingLanguage, Java, Seoul, KOREA
* All rights reserved.
*
* This software is the confidential and proprietary information of Kwangshin
* Oh ("Confidential Information"). You shall not
* disclose such Confidential Information and shall use it only in
* accordance with the terms of the license agreement you entered into
* with Kwangshin Oh.
*/
package kwangshin.codeconvention;
import kwangshin.*;
/**
* 클래스에 대한 설명을 여기에 작성한다.
*
* @version 1.00 2015년 2월 9일
* @author 오광신
*/
public class CodeConvention extends Convention {
/* 클래스의 구현 주석을 여기에 작성한다. */
/** 클래스 변수 classVar1에 대한 설명을 여기에 작성한다. (문서 주석) */
public static int classVar1;
/**
* 클래스 변수 classVar2에 대한 설명이 (문서 주석이)
* 한 줄 이상일 경우 이렇게 작성한다.(접근 제어자가 private일 경우 나오지는 않음.)
*/
private static Object classVar2;
/** 인스턴스 변수 instanceVar1에 대한 설명을 여기에 작성한다.(문서 주석) */
public Object instanceVar1;
/** 인스턴스 변수 instanceVar2에 대한 설명을 여기에 작성한다.(문서 주석) */
protected int instanceVar2;
/** 인스턴스 변수 instanceVar3에 대한 설명을 여기에 작성한다.(문서 주석) 접근 제어자가 private일 경우 나오지는 않음. */
private Object[] instanceVar3;
/**
* ... 생성자 CodeConvention()에 대한 설명을 여기에 작성한다.(문서 주석) ...
*/
public CodeConvention() {
// ... 여기에 실제 코드를 작성한다. ...
}
/**
* ... 메서드 doSomething()에 대한 설명을 여기에 작성한다.(문서 주석) ...
*/
public void doSomething() {
// ... 여기에 실제 코드를 작성한다. ...
}
/**
* ... 메서드 doSomethingElse()에 대한 설명을 여기에 작성한다.(문서 주석) ...
* @param someParam 파라미터에 대한 설명
* @return String 리턴값에 대한 설명
* @exception exception 예외사항에 대한 설명
*/
public String doSomethingElse(Object someParam) {
// ... 여기에 실제 코드를 작성한다. ...
}
}