어떻게 더 좋은 코드를 작성할 수 있을까?
현업에서 1년 남짓 개발을 했던것 같다. 그동안 배운 것이 있다면 속도다. 일정의 압박 속에서 빠르게 원하는 기능을 구현하고 이슈를 처리하는 능력이다. 그런데 빠르게 하다보면 놓치게 되는 것이 많다. 코드가 당장 문제없이 동작하고는 있지만 유지보수, 확장성, 재사용성, 우아함(?) 이런 측면에서는 불안하고 아쉽다. 어떻게 하면 더 좋은 코드를 작성할 수 있을까? 어떻게 디버깅을 더 효율적으로 할 수 있을까? 이 문제를 해결하기 위해 가장 좋은 접근방식은 무엇인가? 빠르게 일을 쳐 내면서도 이런 고민은 뗄수도 없고 늘 마주하게 되는 혹처럼 달고 사는 것 같다. 이러한 질문은 속도와는 반대로 묵직한 시간과 내공이 필요하다.
컴공과에 다시 진학할 수는 없으니 내 전문 분야인 CS를 독학할 수 있는 방법을 리서치해보았다. OSSU(Open Source Society University)라는 것이 가장 눈에 띄었다. 여기서는 컴퓨터 공학에 관한 커리큘럼과 해당하는 온라인 자료를 함께 제공한다. 제시된 온라인 자료는 거의 무료라 하고, 한 주에 20시간을 투자하면 마치는 데 2년이 걸린다고 한다. 역시 세상은 넓고 사람도 많다. 내가 궁금하고 원하는 것들은 찾아보면 대체로 누군가 길을 만들어놓았다. 전체 과정을 따르기엔 조금 벅차서 참고만 하기로 했다.
대신 내가 가장 궁금했던 분야인 디자인 패턴과 아키텍처를 정공법으로 접근하기로 했다. 마침 내가 궁금했던 내용을 딱 짚어주는 강의가 있었다. 코세라에서 제공하는 Software Design and Architecture Specialization이라는 강의를 듣기로 했다. 이 수업을 들으려면 자바를 알아야 해서 수업에서 추천해준 Java Programming: Solving Problems with Software이라는 수업도 병행하기로 했다.
초반의 몇 개 강의를 본 소감은 재밌다!! 나는 지금까지 유데미 파였다. 유데미는 최신 기술과 라이브러리를 적용해서 그럴듯한 결과물을 내는데 가장 좋은 플랫폼이라고 생각한다. 지금까지는 그런게 필요했었다. 이전에 코세라 강의를 봤을 때는 느려서 답답했는데 지금은 머리가 상쾌해지는 느낌이다. 자바는 타입스크립트와 신택스가 유사해서 반갑기까지 하다. 이젠 학습의 페이즈가 바뀌었구나 싶다. 앞으로 두 강의에서 배운 자바 프로그래밍과 소프트웨어 디자인 및 아키텍처에 관한 내용들을 정리하려고 한다.
자바와 친해지기
자바는 객체 지향 언어다. 당신은 클래스와 객체를 사용하게 될 것이다. 클래스는 프로그램을 구성하는 방법이고 객체는 프로그램이 실행될 때 클래스를 이용해서 만들어진다. 클래스는 .java라는 파일 확장자를 갖는다. 여러 클래스의 모음을 패키지라고 한다. 자바 클래스는 메서드를 갖고 있어서 무언가를 실행할 수 있다. 당신이 작성한 코드는 소스코드라고 불리며 high-level 코드다. 사람이 보고 이해할 수 있지만 컴퓨터는 이해할 수 없는 코드다. 컴퓨터가 프로그램을 실행하도록 하려며 소스코드는 low-level 바이트 코드로 번역돼야 한다. 이 번역을 compilation이라고 한다. 바이트 코드는 .class 파일 확장자를 갖는다. 여기서는 BlueJ라는 프로그램을 사용해서 개발할 것이다. 코드 에디터의 복잡한 설정이 없어서 초심자가에게 적절한 프로그램이다.
프로그래밍에서 변수를 선언만 한다면 default 값이 주어지지 않는다. 그래서 변수가 undefined인 것처럼 동작한다. 이런 문제를 위해 자바는 두가지 해결책을 갖고 있다.
- 인스턴스 변수에 0을 초기 기본값을 설정
- 초기화 전인 변수를 사용할 경우 명시적인 에러 발생
그래서 그냥 변수 선언과 초기화를 한번에 하는 것이 바람직하다. 이러한 방식은 모든 프로그래밍 언어에서 문제가 없다.
int x = 4;
자바는 클래스와 객체로 이루어져 있다고 했다. 클래스와 객체와 관련된 용어들을 정리하면서 개념을 잡아보자.
객체는 데이터와 코드를 하나의 단위로 묶은 것이다.
클래스는 객체를 어떻게 만들지 기술해 놓은 템플릿이다.
필드란 객체 안에 있는 변수다. 클래스로 만든 인스턴스 객체 안에 있기 때문에 인스턴스 변수라고도 한다.
private이 붙으면 해당 클래스 안에서만 접근할 수 있다. 이는 중요한 개념인데 현재로써는 모든 필드를 private로 정의하도록 하자.
constructor는 클래스의 객체를 어떻게 생성할 지 기술한다. 이 코드는 객체가 생성될 때 실행되어 객체를 초기화한다. 반환이 없고 이름이 클래스와 동일하다.
method는 클래스 안에 있는 함수다. 자바에서 모든 함수는 클래스 안에 있기 때문에 모든 함수가 메서드다. 메서드는 특정 객체에서 호출되며 해당 객체에 대해서 동작한다.
main은 프로그램 시작점으로, 가장 먼저 실행된다.
위의 개념을 장착하고 아래 코드를 봐보자. 좌표평면 상에서 x, y 포인트에 대한 Point 클래스다.
public class Point {
private int x; // field
private int y; // field
public Point(int startx, int starty) { // constructor
x = startx;
y = starty;
}
public int getX() { return x; }
public int getY() { return y; }
public double distance(Point otherPt) {
int dx = x - otherPt.getX();
int dy = y - otherPt.getY();
return Math.sqrt(dx * dx + dy * dy);
}
public static void main(String[] args) {
Point p1 = new Point(3, 4);
Point p2 = new Point(6, 8);
System.out.println(p1.distance(p2));
}
}
시작점인 main으로 들어가보자. 가장 먼저 p1 변수를 선언하고 초기화한다. 변수를 선언할 때 변수명 앞에 Point가 앞에 붙어있는 것이 보일 것이다. 클래스는 타입이다. 변수를 선언할 때 클래스 타입을 사용할 수 있다.
Point p1 = new Point(3, 4)
이제 다음 라인으로 넘어가서 똑같이 p2 변수를 선언 및 초기화하고 있다. 아래 이미지는 main에서 변수가 생성되는 과정을 포착한 스냅샷이다. 현재는 변수 p1이 생성되어 있으며 p2를 선언까지 한 상태다.
new를 사용하면 데이터가 프레임(frame)이 아니라 힙(heap)에 생성된다. (맨 우측에 있는 작은 Point 박스) 힙에 있는 데이터는 함수가 반환되면서 프레임이 없어져도 남아있다. 아직 x, y 값은 초기화 전이다. 이제 새로운 프레임에서 constructor를 호출할 것이다.
constructor와 메서드는 추가적으로 암시적인 파라미터를 받는다. 작업 대상이 되는 객체를 알려주는 것인데 이 파라미터를 this라고 한다. constructor로 객체의 초기화가 완료되면 이제 변수 p2는 할당할 준비가 되어 우측의 Point 객체를 가리키게 된다. (빨간 선에서 파란 화살표로 바뀌게 됨) 만약 해당 클래스에 constructor가 없다면 객체를 만들고 바로 할당한다.
메서드를 호출할 때 this 파라미터는 닷(.) 앞에 있는 변수와 같은 값을 갖는다. p1.distance(p2)에서 this는 p1이 된다. distance 메서드에서 사용한 Math는 클래스다. Math.sqrt의 this는 뭘까? Math.sqrt는 어떤 객체에 대해서 작용한 것이 아니다. 이러한 것을 static method call이라고 한다.
Types
타입은 데이터가 어떻게 표현되고, 해석되야 하는지 기술한다. 더불어 어떤 작업을 할 수 있는지도 알려준다. 컴퓨팅에서 중요한 룰은 모든 것이 숫자라는 것이다. 즉 bits로 표현된다. 하지만 모든 숫자가 같은 것을 의미하는 것이다. 어떤 것은 숫자, 문자를 의미할 수도 있고 메모리상의 데이터 위치가 될 수도 있다. 타입은 이 숫자들이 어떻게 해석되야 하는지 알려준다.(interpretation) 또한 어떤 것을 할 수 있는지, 어떻게 돼야 하는지도 알려준다.(operation)
Types = Interpretation + Operation
타입을 바꿔야 한다면 어떻게 할까? 어떤 타입은 암시적으로(implicitly) 변환할 수 있다. 컴파일러가 문제 없다고 판단하는 경우다.
int x = 3;
double d = x; // 3.0 implicit conversion
어떤 타입은 변환을 명시적으로 알려줘야 한다. 컴파일러에게 이렇게 바꾸고 싶다고 정확하게 알려주는 것이다.
double d = 3.14;
int x = (int)d; //3
다른 변환은 메서드 호출이 필요하다.
int x = Integer.parseInt("3");
자바 타입에는 두 가지 종류가 있다.
- Primitives: int, double, char, boolean, float, long, byte, short (8가지)
- 값은 박스 바로 안에 있다.
- 메서드를 호출할 수 없다.
- null이 될 수 없다.
- Objects: 위를 제외한 모든 것, String, Point, Shape, 커스텀 클래스, ...
- 객체를 참조한다.
- 닷으로 메서드를 호출하거나 필드에 접근할 수 있다.
- null이 될 수 있다.
- ==는 화살표가 동일한 객체를 가리키고 있는지 체크한다.
For Each Loop
for loop은 for each loop이라고도 한다. iterable의 각 값에 대해서 무언가를 하기 때문이다. iterable은 연속된 값을 제공해주는 객체다.
public class HelloWorld {
public void runHello() {
Fileresource f;
f = new FileResource("file.txt");
for(String line : f.lines()) {
System.out.println(line));
}
}
public static void main(String[] args) {
HelloWorld hw = new HelloWorld();
hw.runHello();
}
}
출처
Java Programming: Solving Problems with Software, https://www.coursera.org/learn/java-programming
댓글