4. 숫자 자리수 잉여 구분자
21세기 들어서 프로그래밍 언어들에 알음알음 몰래 도입돼 들어간 요소 중 하나로는.. 숫자 자리수를 구분하는 잉여 구분자가 있다.
가령, Java는 _ 밑줄을 이 용도로 지원한다.
그래서 a = 1234567890이라고 쓸 것을 a = 12_3456_7890이라고 써도 되고, a = 1_234_567_890이라고 써도 된다. 소수점도 3.141_592_653 이렇게 쓸 수 있고, 0xFFFF_0000처럼 타 진법도 마찬가지이다.
C++에서는 참 흥미롭게도 '(어퍼스트로피)를 동일 용도로 지원한다. C++11에서인가 추가됐다고 한다. a=1'234'567;
_은 공백을 염두에 둔 기호인 반면, '는 콤마를 염두에 둔 기호라는 차이가 있다.
이런 구분자는 컴파일러의 입장에서는 있건 없건 토큰을 인식하는 데 아무 차이가 없다. 주석과 동급으로 전혀 필요하지 않은 잉여일 뿐이다.
단지, 사람 입장에서의 가독성을 위해서 이 구분자만은 예외로 컴파일러가 이물질이라고 토해내지 않고 무시 처리해 주는 것이다.
64비트 double 부동소수점이야 16비트 시절부터 존재했겠지만, 64비트 정수 리터럴은 쌍팔년도에는 거의 볼 수 없는 물건이었지 싶다. 이 정도로 큰 수는 10진법으로 나타내도 글자 수가 거의 20자에 달하게 된다. 그러니 필요할 때 인위로 자리수를 끊어서 표기할 수 있다면 코드의 가독성 차원에서 깨알같은 도움이 될 것이다.
다만, 학술적인 의미가 있지 않고 사람이 값을 참조할 일이 없는 단순 난수표 같은 숫자 테이블이라면 굳이 자리수 구분해서 적을 필요가 없을 듯하다.
그러고 보니 C++에는 0b로 시작하는 2진법 리터럴 표기도 추가됐다. 얘는 아무래도 길이가 굉장히 길기 때문에 8자 단위로 '로 끊어서 표기하는 게 확실히 유용하겠다.
그에 비해 C 시절부터 존재했던 8진법 표기는 진짜 아무데서도 안 쓰이는 잉여가 된 것 같다. FTP 파일 권한 777 이런 것 말고 딴 데서는 도통 본 적이 없다.
참고로 C/C++에는 줄 바꿈 문자를 없애고 토큰을 한데 이어 주는 \ 역슬래시라는 강력한 기호가 있다.
C/C++은 태생적으로 줄 바꿈에 연연하지 않고 중괄호와 세미콜론으로 문장을 구분하기 때문에 \ 가 필요할 일이 그리 많지는 않다. 사실상 #define 매크로 함수를 여러 줄에 걸쳐 길게 선언하는 용도로만 쓰인다.
하지만 그 특성상,
int a=123\
456;
const char b[]="abc\
def";
이렇게 써 줘도 얘는 a=123456이라고 인식되며, b에는 "abcdef"가 들어간다. \는 컴파일러라기보다는 거의 전처리기 수준으로 소스 코드의 두 줄을 기계적으로 연결해 준다고 생각하면 된다.
이걸로 심지어 // 주석조차 다음 줄까지 계속되게 만들 수 있으니 말 다 했다.;;
참고로, 주석은 컴파일러의 입장에서 whitespace 하나로 간주된다. 그렇기 때문에 100/*ㅋㅋㅋㅋ*/00은 100과 00을 분리시키며, 100'00과 같은 역할을 할 수 없다.
객체 지향, 제네릭/메타프로그래밍, 함수형 등 갖가지 패러다임들이 C++, C#, Java 등 메이저 언어들에 다 도입되면서 프로그래밍 언어들은 서로 비슷해지는 '수렴 진화' 중인 것 같다. 물론 자기 고유한 정체성을 상실할 정도로 완전히 똑같아지지는 않겠지만 말이다.
5. 오타
현직 프로그래머 내지 소프트웨어 엔지니어는 코딩을 한다고 해서 맨날천날 시간 복잡도, 공간 복잡도 따지고 다이나믹이니 그리디니 하는 신선놀음 같은 알고리즘 고민을 하는 게 아니다.
현실에서는 알고리즘이야 이미 만들어져 있고 잘 돌아가는 검증된 라이브러리나 오픈소스를 가져와서 쓰는 게 훨씬 더 많다.
자기가 새로운 코드를 만들어 내는 것보다 남이 만든 기존 코드를 읽고 유지보수 하고 버그를 잡는 비중이 훨씬 더 크다.
그리고 그 와중에 그나마 새로운 코드를 작성하는 게 있다면.. "뭔가 이름을 붙이는 것"의 비중이 매우 크다. 동사구이든 명사구이든..
그러니 프로그래머가 자기 조직이 마음에 안 들 때 아주 교묘하게 사보타주를 하고, 자기 후임을 엿먹이고 생산성을 저해하는 효과적인 방법이 있다.
자기가 작성하는 각종 클래스, 함수 등의 이름에다가 고의로 오타를 교묘하게 집어넣는 것이다.
아주 간단하게 getUserAdress 라든가.. receiveIncommingMessage 따위.
...;; 프로그램이야 멀쩡하게 돌아가니까 그 당시에는 아무 문제가 없는데..
문제는 나중에 그 프로그램의 버그를 잡고 기능을 추가하는 등 유지보수를 할 때다.
대놓고 약어를 쓴 것도 아니고 원래 그대로 풀어 쓴 듯한 영단어가 미묘하게 스펠링이 여기저기 틀려 있으면..
나중에 "검색"이 안 되어서 미치고 펄쩍 뛰는 일이 야기된다.
이런 코드는 여러 사람을 거쳐 가며 작업을 하기 어려우며, 처음 짰던 사람이 아니면 구조를 쉽게 파악할 수 없게 된다.
도서관에서 책을 꺼냈다가 일련번호 순서가 아닌 아무데나 꽂아 넣는 것과 같은 일이 벌어진다. (잘못 꽂힌 책은 없는 책과 같습니다)
진짜.. 개발 환경에서는 프로그래밍 언어 차원에서 코드의 문법 오류만 빨간줄을 치는 게 아니라, 명칭의 영어 스펠링 오류를 체크하는 것도 꽤 도움이 되지 싶다.
먼 옛날에 컴퓨터가 너무 비싼 물건이고 텍스트 에디터의 인터페이스가 불친절· 불편하고 디스크 공간이 부족하던 시절에는
뭐든지 getpid() 이런 식으로 짧게 줄여 쓰는 게 관행이었다. PC통신 채팅이나 전보에서 '안냐쎄여' 등으로 필사적으로 줄이는 것의 코딩 버전이나 마찬가지이다.
그러나 디스크 용량 걱정이 없어지고, 한번만 명칭을 정한 뒤부터는 에디터에서 긴 명칭을 자동 완성해 주는 기능이 매우 편리하게 발달하고(거의 90년대 말.. =_=), 또 소프트웨어의 규모가 왕창 방대해지고 공동 작업의 중요성이 커진 뒤부터는 GetProcessID() 이렇게 길게 풀어 쓰는 게 더 바람직한 관행으로 정착했다.
소스 코드가 자연어와 더 비슷해지고 길어지고 나니 스펠링 오류에 대한 취약성도 더 커진 셈이다.
6. 함수 안에 함수, 클래스 안에 클래스
파스칼 내지 Ada 같은 옛날 구시대 언어 중에서는 함수 안에 함수를 만드는 걸 지원하는 경우가 있었다. 그에 비해 C는 함수 호출 구조를 단순화시키느라 그런 걸 제공하지 않았다. 한 함수 안에서만 잠깐 쓰이는 코드 반복 패턴을 표현하려면 그냥 매크로 함수를 쓰라는 취지였던 듯하지만.. 이건 막 깔끔한 해결책은 못 됐다.
오늘날은 함수형 프로그래밍이 도입되면서 람다 덕분에 함수 안에 함수를 넣는 게 '사실상' 가능해졌다. 다만, 예전에 생각했던 그런 문법이 아니라, 함수 몸체를 지역변수에다 대입하는 굉장히 이색적인 형태로 가능해졌다는 게 신기한 점이다.
함수와 달리, 클래스는 원래부터 자기 내부에 클래스를 또 가질 수 있다. 그래서 C++은 C와 달리 계층적인 다단계 scope을 구현할 수 있으며, 필요에 따라 이거 표현을 간소화하기 위해 using이라는 키워드도 도입됐다.
함수건 클래스건.. (1) 내부에 안겨 있는 녀석의 명칭은.. 걔를 품고 있는 outer의 문맥에서만 유효하고 거기서만 접근 가능하다. 이건 너무나 당연한 이치이다.
그런데 안겨 있는 녀석은.. (2) 반대로 자기를 안고 있는 outer의 멤버(클래스의 경우)나 변수(함수의 경우)에 접근 가능해야 한다.
C/C++은 (2)를 지원하는 것이 미흡하고 인색했다. 그래서 C 시절부터 함수 안에 함수 같은 건 골치 아프니 지원하지 않았으며, 클래스 안의 클래스도 바깥 클래스의 인스턴스 멤버로 접근을 지원하지 않았다. Java로 치면 static class밖에 지원하지 않은 것과 같다.
C/C++은 포인터를 그렇게도 좋아하는 언어인데, 저것들은 정수 하나짜리 날포인터만으로 구현할 수 없는 개념이어서 지원을 안 한 것이지 싶다.
static 함수야 클래스에만 소속됐지, 클래스의 각 인스턴스에 매여 있지는 않아서 this 포인터가 존재하지 않는 함수이다(0). 사실상 전역 함수이나 마찬가지이다.
그리고 일반적으로는 자기 자신을 가리키는 this는 메모리 주소 딱 1개만 가리키는 포인터이지만.. 하지만 객체지향을 제대로 구현하려면 this의 크기가 한 칸만으로 충분하다는 고정관념을 깨야 할 것 같다. inner class라든가 다중 상속은 이런 문제를 훨씬 더 복잡하게 만들기 때문이다.
하긴, 그래서 Java에서는 C++에 없는 Outer o = (new Inner()).new Outer(); 이런 코드가 가능하다. C++에서는 new 연산자를 오버로딩 하더라도 무조건 static 형태만 되는데, Outer는 this가 자기 자신뿐만 아니라 Inner까지 사실상 두 파트로 구성되는 셈이다.
이게 가능하니 C++ 같았으면 다중 상속을 해야 했을 것도 저렇게 퉁치고, 프로그래밍을 더 작은 객체 단위로 깔끔하게 할 수 있을 것 같다.
클래스는 그렇다 치고.. 함수가 outer의 변수에 접근하는 건 요즘 C++도 '캡처'라는 기능으로 제공하기 시작했다. 원래는 '클로저'라고 부르는 개념이었지 싶은데 말이다. 이건 프로그래머/사용자의 관점에서는 아주 편리한 기능이지만, 내부적으로는 역시 함수 실행 문맥을 가리키는 포인터를 집어넣고 어쩌구 하면서 꽤 힘든 과정을 거쳐서 구현된다.
Posted by 사무엘