안전한 데이터를 사용하라 일반적으로 사용하는 배열의 경우 index가 배열의 크기를 넘어설 수 있다는 위험을 안고 있다. 특히 char배열을 사용한 문자열인 경우 쉽게 그 범위를 벗어나는 경우가 많다. char *unsafe_copy(const char *source) { char *brffer = new char[10]; strcpy(buffer, source); return buffer; } 책에서 위와 같은 소스를 예로 들었다. 이러한 내용은 다른 데이터구조에 겹쳐 써질 수 있으며 악의적인 사용자에 의해 스택에 실행가능한 코드를 올려놓고 그 코드를 이용해 마음대로 실행시키는 방법으로 사용될 수 있단다. 무섭다. string클레스처럼 관리기능이 있는 버퍼를 사용하라. 그러한 것이 없다면 최소한 strncpy처럼 크기제한이 있는 operation을 사용하라.
모든 리턴 값을 체크하라 printf나 scanf같은 함수들도 리던값이 있다. 모조리 다 체크하라는 것이 아니라 return이 있으면 최대한 활용하라는 것이다.
변수는 선언지점에서 초기화
변수를 가능한 늦게 선언하라 불행히도 C언어처럼 코드 중간에 변수의 선언을 인정하지 않는 언어도 있지만 대부분 중간에 선언하는 변수들도 인정해준다. 1. 반복문 안에 사용하는 변수를 밖에서 선언하지 마라. for(int i = 0;...)과 같이 사용하라는 말이다. 2. 임시 변수를 여러곳에서 재사용하지 마라. temp라는 이름의 변수 아깝다고 여기저기서 재사용했던 기억이 난다. 그런거 하나도 아까울 것 없다. 임시변수라고 해도 한곳에서 한가지로 써라. *** 효율성에 관한 문제는 컴파일러가 처리한다. 컴파일러를 믿어라. 그거 꽤 공부 많이 한 사람들이 머리 싸매고 같이 만든 것들이다.
언어의 표준을 생각하라. C와 C++이 특히 심하다. 컴파일러마다 표준을 포함하고는 있지만 다른 추가기능들을 보태서 만들어버린다. 표준이 나중에 나와서 그런 것 같다. 심지어는 표준에 맞추어 작동하지 않는 경우도 있다. 컴파일러의 특정한 기능에 의존하면 안돌아가는 코드 만들어놓고 연장 탓하는 프로그래머가 될 수 있다. 우리가 많이 사용하는 MS visual studio 6.0버젼에서는 반복문 안에 선언된 변수가 다른 곳에서 다시 선언되면 에러를 내고 템플릿도 표준에 맞추어 사용되지 않고 있다.
Cast는 신중하게. 대부분의 언어는 타입케스팅을 허용한다. (int)double_num; 이런걸 허용한다는 말이다. 강제로 모양을 바꾸는 것이니 조심해야 한다. 그 순간만 돌아간다고 넘어가면 다른 곳에서 재사용할 때 반드시 문제가 생긴다.
언어의 관용구(idiom)따르기 많은 사람들이 사용하는 단어를 사용하라. 익숙한 말은 오해를 줄여준다.
수치의 범위 체크하기 1. 수치 변수의 오버플로나 언더플로를 조심하라. 확실히 알고 사용하라. (얼마 전 파일을 byte단위로 달라서 배열에 저장하는 프로그램을 작성한 적이 있었다. 용량이 작은 것은 문제가 없었는데 index범위가 int의 범위를 넘어서자 문제가 생겼다.) 2. 각각의 계산이 완전한지 체크하라. 0으로 나누는 에러등을 생각하라
***********제약*********** 1. 배열에 대한 모든 접근이 배열의 범위 안에서 이루어지는지 체크 2. 포인터로 데이터에 접근하기 전에 포인터의 값이 NULL인지 확인 3. 객체에 대한 operation을 하기 전에 그 객체의 상태에 모순이 없는지 증명 (연산자 오버로딩을 할 경우 연산자 양쪽에 각은 객체가 있을 경우도 생각해야 한다.) 4. 파라미터와 리턴값을 제대로 확인하자.
① 정수(整數)에 대해서는 다른 정수의 제곱으로 이루어져 있는 정수를 말하는 것으로, 예컨대 1(=12), 4(=22), 9(=32), 16(=42),…이다. ② 다항식(多項式)에 대해서는 어떤 다항식의 제곱으로 되어 있는 다항식을 말하는 것으로, 예를 들면 f(x)=x2+6x+9=(x+3)2일 때, g(x)=x+3이면, f(x)={ g(x)} 2이 된다. 이 때 f(x)는 완전제곱이다.
- ⓒ 두산백과사전 EnCyber & EnCyber.com, 무단 전재 및 재배포 금지 -
인터넷 카페를 돌다가 누군가 완전제곱 판별 소스를 올려놓은 것을 봤다. 소스를 보면서 문제를 풀 때 바로 컴퓨터 앞에서 작성을 시작하는 것이 얼마나 위험한 일인지 다시 생각하게 되었다. 아래는 까페 관리자가 올린 소스다. 프로그래밍에 꽤 신경을 많이 쓰는 것이 보이는 사람인데 문제를 풀 때는 가끔 실수를 하기도 하니까.. 만약 이 글을 본다면 기분나쁘게 생각하지 말았으면 좋겠다.
#include <stdio.h> #include <math.h>
int IsPerfectSquareNumber(unsigned long number); int IsPrime(unsigned long number);
좀 더 많은 수를 입력받을 수있게 하기 위해 unsigned long을 사용하고 있는 것을 알 수 있다.(int는 컴파일러에 따라 다른 결과를 가져올 수도 있다.) 무리하게 if문 내에 핵심내용을 기술하는 것 외에도 치명적인 실수가 있다. 프로그램이 실행되는 동안 불필요한 작업을 반복해서 하고 있는 부분이 있다. 도대체 어디가? 아래 글을 보기 전에 한번 차근자근 따져보기 바란다.
IsPrime함수의 사용이다. IsPerfectSquareNumber함수 내에서 for문을 돌면서 2부터 1씩 증가시키는 값으로 나누어질수 있는만큼 계속 나누어간다. 소인수분해는 소수(Prime number)로 나누어야 한다. 그런데 2로 나눈 나머지가 다시 2로 나누어지면 반복해서 나누는 과정이 있다. for(j = 0; (number%i)==0; j++) number/=i; 이 부분이다. 이 구문을 만나면 소수가 아닌 숫자로는 for문의 판별식((number%i)==0)을 만족할 수 없으므로 애초에 for문에 들어가지 못한다. 예를 들어 8로 나누어지는 수라면 이미 2로 3번 나누었다. 6으로 나누어지는 수라면 2에서 한번 3에서 한번 나누었으므로 i가 6까지 왔으면 당연히 나눌수 없게된다.
그래서 소스를 조금 수정해봤다.
#include <stdio.h> #include <math.h> int IsPerfectSquareNumber(unsigned long number);
int main(void) { unsigned long number; printf("자연수 하나를 입력하십시오 : "); scanf("%lu", &number); printf("%lu는 완전제곱수%s니다.\n", number, IsPerfectSquareNumber(number)?" 입":"가 아닙"); return 0; } int IsPerfectSquareNumber(unsigned long number) { unsigned long i, j; for(i=2; i<=number; i++) { for(j = 0; (number%i)==0; j++) number/=i; if((j % 2) != 0) return 0; } return 1; }
여기서 if문은 어떤 수가 되건 어떠한 값(소수가 아니더라도)의 제곱이 되면 완전제곱이므로 같은 수가 쌍으로 있으면 완전제곱이 될 수 있다는 생각이다. 예를 들어 144(= 12*12)는 2*2*2*2*3*3이므로 2^4*3^2으로 표현된다. 2가 두쌍 3이 한쌍 있으므로 완전제곱이 된다.
이래놓고 봐도 도저히 마음에 들지 않는다. 이럴 땐 문제를 처음부터 다시 인식하는 과정이 필요하다. 완전제곱의 정의 : 다른 정수의 제곱으로 표현되는 정수 1조건 : 정수여야 한다. 2조건 : 다른 정수의 제곱으로 표현된다.(다시 말하면 기하평균이 정수다.)
이렇게 생각해보면 기하평균이 정수인지 확인해보면 되는 것이다. 기하평균이란 어떠한 수의 제곱근을 말하는 것으로 루트를 씌운 값이라고 생각하면 쉽다. 기하평균이 정수인지는 어떻게 판단하지? 난 아래와 같은 코드를 생각했다.
#include <stdio.h> #include <math.h>
int IsPerfectSquareNumber(unsigned long number);
int main(void) { unsigned long number; printf("자연수 하나를 입력하십시오 : "); scanf("%lu", &number); printf("%lu는 완전제곱수%s니다.\n", number, IsPerfectSquareNumber(number)?" 입":"가 아닙"); return 0; }
int IsPerfectSquareNumber(unsigned long number) { unsigned long ga = (unsigned long )sqrt(number); //ga == geometric_average return ((ga * ga) == number); }
기하평균을 정수로 변환시킨 후 다시 제곱하여 원래의 수가 나오는지 확인하는 것이다. 만약 기하평균이 정수가 아니라면 slicing이 발생하여 소수점 아래는 버린다. 그러면 제곱을 해도 원래의 값이 되지 않는다. 형 변환의 위험을 역이용하였다. 위험한거 아냐?라고 생각한다면 큰 오해다. 마지막 소스는 수학적으로 문제가 없으며 처음보다 훨씬 안전한 소스라고 생각한다.
꽤 길고 복잡한 소스가 두줄로 줄었다. 심지어 이정도라면 함수를 따로 구현할 필요조차 없이 그냥 한줄로 넣어주면 된다. 문제를 제대로 인식하고 어떤 방법으로 접근할 것인가를 깊이 생각해보는 것은 프로그래머의 중요한 습관이다. 열심히 생각하고 노력하는 사람들도 실수를 많이 하는데 처음부터 함부러 뛰어드는 습관이 들어버린다면 당신은 프로그래머가 아니라 코더로 만족해야 한다.
익스트림 프로그래밍(XP, eXtreme Programming) 이라는 방법은 최근의 소프트웨어 개발 방법론 분야에 새로 등장했습니다. 많은 사람들이 "프로그래머들이 정말 원하는 방법"이라고 하는 익스트림 프로그래밍(XP)은 90년대 말에 등장했으며 2명으로 구성된 작은 회사에서 포드 자동차에 이르기까지 다양한 규모의 회사에서 쓰이고 있습니다 익스트림 프로그래밍(XP)의 가장 큰 장점은 막판에 스펙이 변경되는 일이 있어도 고객이 원하는 것을 고객이 원하는 기한에 맞춰서 제공할수 있다는 점입니다
익스트림 프로그래밍(XP)는 서로 조화롭게 쓸수 있도록 계획된 일련의 규칙이 있습니다 물론 그 가운데 일부만을 채택하고 있는 프로그래머도 많이 있긴 합니다.
이런 규칙들에는 다음과 같습니다
.조금씩, 하지만 자주 발표한다 .사이클을 반복해서 돌리면서 개발한다 .스팩에 없는 것은 절대 집어 넣지 않는다 .테스트 코드를 먼저 만든다 .야근을 하지 마라. 항상 정규 일과 시간에만 작업을 한다 .기회가 생기는 족족 언제 어디서든 코드를 개선한다 .모든 테그트를 통과 하기 전에는 어떤 것도 발표하지 않는다 .조금씩 발표하는 것을 기반으로 하여 현실적인 작업 계획을 만든다 .모든일을 단순하게 처리한다 .두명씩 팀을 편성하고 모든사람이 대부분의 코드를 알수 있도록 돌아가면서 작업한다.
Software internationalization is difficult under the best of circumstances, but it always amazed me how often one particular country came up in discussions of internationalization problems: Turkey.
I've been tracking a really funky bug in my West Wind Web Store application that seems to crop up only very infrequently in my error logs. In a previous post I mentioned that I had instituted some additional logging features – specifically making sure that I would also log the locale of the user accessing the application.
Well, three bug reports later I noticed that all errors occurred with a Turkish (tr) browser. So I changed my browser's default language to Turkish and sure enough I could see the error occur.
I had blogged earlier about a bug in dasBlog that affected Turkish users. When a Turkish browser reported an HTTP Accept-Language header indicating Turkish as the preferred language, no blog posts would show up. As fix, I suggested that users change their blog templates, but I knew that wasn't an appropriate fix.
I understand that Turkish prisons are not to be trifled with, but the question remains: why do Turkish people take such cruel and perverse delight in breaking our fine software? What's wrong with Turkey?
As with so many other problems in software development, the question shouldn't be what's wrong with Turkey, but rather, what the hell is wrong with software developers? Some of this is sort of obvious if you have any cultural awareness whatsoever.
In the United States, we would typically format today's date as 3/14/2008. In Turkey, they format it as 14.3.2008.
In the United States, we use commas to group digits, like so: 32,768. In Turkey, they group digits using a period, so the same number would be entered as 32.768.
These minor formatting differences are usually not a big deal for output and display purposes, but it's a whole different ballgame when you're parsing input. You'd naturally expect people to input dates and numbers in the format they're used to. If your code assumes that input will be in typical American English format, there will be.. trouble.
Most languages have this covered; there are functions that allow you to read or write dates and numbers appropriately for various cultures. In .NET, for example, it's the difference between these two calls:
Because no culture is specified, the first call will parse the number according to the rules of the default culture that code is running under. Let's hope it's running under a Turkish version of Windows, so it can parse the number correctly. The second call, however, explicitly specifies a culture. The "invariant" culture is every American programmer's secret dream realized: we merely close our eyes and wish away all those confusing languages and cultures and their crazy, bug-inducing date and number formatting schemes in favor of our own. A nice enough dream while it lasts, but instead of rudely asking your users to "speak American" through the invariant culture, you could politely ask them to enter data in ISO international standard format instead.
Anyway, point being, this kind of culture support is baked into most modern programming languages, so all you need to do is make sure your developers are aware of it-- and more importantly, that they're thinking about situations when they might need to use it.
But all that date and time formatting stuff is easy. Or about as easy as i18n ever gets, anyway. Strings are where it really starts to get hairy. Guess where this code fails?
switch (myType.ToLower())
{
case "integer" : ;
}
If you guessed Turkey, you're wrong! Just kidding. Of course it fails in Turkey. When we convert the string "integer" to upper and lower case in the Turkish locale, we get some strange characters back:
It's sort of hard to see the subtle differences here unless we ratchet up the font size:
I → lowercase → ı
i → uppercase → İ
There's obviously no way these strings are going to match "integer" or "INTEGER" respectively. This is known as the Turkish I problem, and the solution should feel awfully familiar by now:
That will produce the expected output, or at least, the output that matches the comparison in the original code snippet.
This is, of course, only the tip of the iceberg when it comes to internationalization. We haven't even touched on the truly difficult locales like Hebrew and Arabic. But I do agree with Jeff Moser-- if your code can pass the Turkey test, you're doing quite well. Certainly better than most.
If you care a whit about localization or internationalization, force your code to run under the Turkish locale as soon as reasonably possible. It's a strong bellwether for your code running in most-- but by no means all-- cultures and locales.
======================================================================== 같은 소스를 컴파일해도 터키에서는 돌아가지 않는단다. 언어의 문화적 차이 때문이다. 소수점을 마침표(.)와 쉼표(,)로 구분하는 차이, ToLower, ToUpper등 언어의 인식 차이(미쿡에서는 ToUpper를 소문자에서 대문자로 바꾸는 것으로 인식하지만 터키에서는 글자의 크기를 크게 만드는 것으로 인식한다.) 같은 코드를 컴파일하면 에러가 발생하는데 그나마도 브라우저를 터키어로 맞추지 않으면 볼 수 없단다. .NET에서는 그러한 문화적 차이를 표시해주는 코드가 포함되어 있는데 'Invariant'가 그것이란다.
한국도 마찬가지다. 많은 한국인들이 다분히 미쿡을 동경하지만 언어에 있어서는 한국어를 사랑한다. 일상생활에서 영어를 능숙하게 말하고 다니면 '어머 웬 잘난척? 즈질이야~'정도의 말을 듣는다.(나도 그런말 들어보고 싶다.) 한국어를 좋아하고 주로 사용하는 것은 좋은 현상이다. 그런데 상황에 맞게 사용해야 한다. 프로그래밍을 할 때 시간표시나 숫자표시등이 일치되지 않는 부분이 꽤 있다. 시간표현은 표현방식이 아주 다양하다. 24시표현, AM/PM, 오전/오후 시:분, 시/분, 시-분, 시 분 이렇게 다양한 표현을 모두 한국 문화에서 사용한다. 숫자표현은 어떤가? 그냥 숫자를 바로 사용하기도 한다. 그건 세계 공통이다. 하지만 숫자의 자릿수를 좀 더 명확히 하기위해 ,로 세자리 단위씩 끊어서 표현한다. 그리고 읽을 땐 4자리 단위로 읽는다. 한국에서 사용하는 숫자의 단위는 4단위다. 일, 만, 억, 조, 경, 해... 그런데 숫자는 3단위다. 미쿡에서는 숫자의 단위가 3단위다. 어렸을 적 새로운 단위가 제대로 정착되는데 혼란이 없다면 표기단위를 4단위로 하는 것이 좋을 것이라고 생각했었다.
글에서 문화적 차이를 뛰어넘으려면 ISO표준을 따르라고 권하고 있다.
물론 국제표준을 따르는 것이 가장 좋다. 그리고 그렇게 하지 못할 땐 입장을 확실하게 해야 한다. 무슨 말이냐면 한국식으로 하고싶으면 해당 scope내에서는 확실히 한국식으로 하고, 미쿡식으로 하고싶으면 확실히 미쿡식으로 하고 ISO대로 하고싶으면(ISO표준이 미쿡식은 아니다) ISO에 맞추어서 하란 말이다. 가끔 실무에서도 변수 이름으로 한국어발음을 억지로 영어로 적어놓은 경우를 자주 볼 수 있다. (실제 이런 이름을 쓰진 않겠지만 굳이 예를 든다면 show, view, print등의 내용을 bogi(보기)와 같이 억지로 쓰는 경우가 있다.)
상대가 정 모르면 사전을 찾아서라도 알 수 있으면 그건 괜찮다. 자신이 있지도 않은 단어를 만들어버리는 도대체 어떻게 알아보란 거야? 이도 저도 아닌 어중간한 지점에서 타협하지 마라!!