Monday, December 28, 2009

한국의 S/W 품질이 낮은 이유

나는 한국의 소프트웨어 품질이 낮다고 생각한다. 건축가가 자신이 설계한 건물에서는 못잔다는 말처럼 직업이 프로그래머이니까 그렇게 느낄 수도 있겠다고 생각할지 모르겠으나, 시간이 지나면 지날 수록 이러한 생각이 확고해 지기만 할 뿐이다.

소프트웨어 품질이 낮다고 동의하는 대부분의 사람들이 개발자의 수준을 먼저 얘기하곤 한다. 심지어 개발자들도 스스로의 수준을 얘기하는 경우가 더러 있다. 하지만, 이건 아니다. 수압이 낮아서 물이 안나오는 데 애꿋은 수도꼭지 탓만 하는 것과 비슷하다.

한국의 소프트웨어 품질이 낮은 이유는 프로그래머의 수준보다는 프로그래머의 의견이 무시되는 개발속도/기능 위주의 개발 환경 때문이다.

프로그램 개발에 관련해 여러사람들과 얘기하다보면 항상하는 얘기가 있다. 어느정도 규모나 완성도를 요구하는 소프트웨어의 개발은 고층건물을 짓는 것과 비슷하다는 얘기다. 단층 주택은 별 고민 없이 뚝딱뚝딱 지을 수 있다. 하지만 100층짜리 건물은 얘기가 다르다. 지반 공사 부터 시작해서 하중 계산, 고층에서의 진동, 내장재의 물리적 특성, 전기 분배, 상하수 배관 시설, 낸난방 및 공조 등의 각 부분에서 물리, 전기, 인체공학 등등의 여러 기술이 복합적으로 사용되어 건물이 완성된다. 프로그램도 마찬가지다. 조엘 온 소프트웨어에는 웹페이지에서 간단한 파일을 업로드 하는 페이지를 예로들고 있다. 수 Kb의 파일을 업로드하는 코드는 10분이면 완성이다. 하지만 Gb라면 어떨까. 파일 업로드 상태를 모니터하는 쓰레드와 서버와 주기적으로 통신하여 상태를 확인하는 코드, 업로드 중에 발생하는 여러 상황에 대한 고려, 업로드 실패시 전송된 부분에 대한 후처리 등 생각할 일이 많다.

하지만 이를 인식한 개발자의 의견이 반영되는 경우는 매우 드물다. 일반적으로는 그냥 업로드 파일의 크기를 수 Mb로 제한하고 대신 다른 다양한 기능을 더 추가하는 방향으로 진행된다. 기능적인 완성도를 추구하는 개발보다는 여러 가지 기능들을 빠른 시일에 화려한 UI와 개발하는 방향으로 진행된다. 굳이 일일이 예를들지 않아도 우리식(?)으로 개발된 프로그램들을 보면 대부분 이런 식이다.

A라는 거대 고객이 X라는 프로그램을 만들어 달란다. B사는 요구하는 기능을 충실히 구현하고 에러가 발생하지 않도록 많은 상황을 고려하여 신뢰성있는 소프트웨어를 개발하는데 집중한다. C사는 B사의 소프트웨어 보다 더 많은 기능을 넣고 화려한 UI로 장식한다. 고객 입장에서 최종 제품을 테스트 해보니 둘다 원하는 기능이 동작하는데 C사는 더 많은 기능을 제공하면서 더 이쁘다. 그래서 C사를 선택한다. 나중에 고객은 미처 예상하지 못했던 오류를 C사의 제품에서 발견한다. B사는 이러한 경우까지 고려했다고 처음에 설명했지만 고객은 오류를 직접 당하기 전까지는 이해하지 못한다. 결국 이런식의 제품 개발이 반복되어 B사도 다기능의 화려한 외양의 소프트웨어 개발에 매진하게 된다.




Friday, December 25, 2009

Brief on Signal

Review SIGNAL

Signal can be used to control process. SIGKILL, SIGSTOP, SIGINT are well known types of signal. No process can mask SIGKILL and SIGSTOP.

In a program, you can register signal handler function so that when a specific signal is sent to a process it can invoke registered signal handler function.

And also you can block in a certain part of the code to wait signal.

In multi-thread program, we can set signal mask so that only specified thread can handle the signal.

* How to register signal handler.

Though we've used the function signal() before, it's ANSI compatible one but not a standard. We can use sigaction() in the standard.

sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

If handler is registered with the function above, handler will be called when the signal occurs.

We use struct sigaction structure to register handler, to set mask, flags.

struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *); // kind of advanced
    // used with sigfillset(), sigemptyset(), sigaddset(), sigdelset()
    sigset_t sa_mask;  
    int sa_flags; // option flags like
    /*
        SA_NOCLDSTOP, // child의 중지시 SIGCHLD 안받음
        SA_ONESHOT/SA_RESETHAND : 일회용 핸들러
        SA_NOMASK, SA_NODEFFER : 같은 시그널 발생을 block하지 않음
        SA_SIGINFO : 핸들러로 sa_sigaction을 사용
    */
    void (*sa_restorer)(void);
};


Sample code

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>

void usr_handler(int signum);

int main()
{
    struct sigaction sa_usr1;
    struct sigaction sa_usr2;

    memset(&sa_usr1, 0, sizeof(struct sigaction));
    sa_usr1.sa_handler = usr_handler;
    sa_usr1.sa_flags = SA_NODEFER;
    sigfillset(&sa_usr1.sa_mask);

    memset(&sa_usr2, 0, sizeof(struct sigaction));
    sa_usr2.sa_handler = usr_handler;
    sa_usr2.sa_flags = SA_NODEFER;
    sigemptyset(&sa_usr2.sa_mask);

    sigaction(SIGUSR1, &sa_usr1, NULL);
    sigaction(SIGUSR2, &sa_usr2, NULL);

    printf("SIGUSR1, SIGUSR2 Registered!\n");

    for (;;)
    {
        pause();
    }

    return EXIT_SUCCESS;
}

void usr_handler(int signum)
{
    int i;
    for (i = 0; i < 5; i++)
    {
        printf("[%d]Caught signal. will sleep 1 sec : SIGUSR%d\n"
            ,i , (signum == SIGUSR1) ? 1 : 2);
        sleep(1);
    }
}


* How to block signal in a process

sigprocmask() offers signal blocking.

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

If oldset is not NULL, current signal mask will be copied into oldset.

* How to handle signal in multi-thread program.

As a default, if program receive signal, all of its thread receive the signal and it will make,. a chaos and program will halt. Generally, people set signal mask so that signal can be handled by specific thread.

Like sigprocmask, threre is pthread_sigmask(). If it's called in main(), all thread created after will have that signal mask. It it's called in a certain thread, only that thread will have that mask. In this way, we can specify which thread will receive signal.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <pthread.h>

#define THREADNUM 5
#define LIFETIME 10

struct s_thread_ctx
{
    pthread_t tid;
    int index;
}ctx[THREADNUM];

void sig_handler(int);
void *worker(void *);

int main()
{
    void *res;
    int i;
    sigset_t sigmask, sigmask_old;

    sigfillset(&sigmask);

    printf("[MAIN] Masking all signals\n");
    pthread_sigmask(SIG_SETMASK, &sigmask, &sigmask_old);

    for (i = 0; i < THREADNUM; i++)
    {
        ctx[i].index = i;
        pthread_create(&ctx[i].tid, NULL, worker, (void *)&ctx[i]);
        printf("[MAIN] Create %dth thread\n", i);
    }

    printf("[MAIN] Joining threads\n");
    for (i = 0; i < THREADNUM; i++)
    {
        pthread_join(ctx[i].tid, &res);
    }
    return 0;
}

void *worker(void *arg)
{
    int i;
    sigset_t sigmask;
    struct s_thread_ctx *ctx;
    struct sigaction sa;

    ctx = (struct s_thread_ctx *)arg;

    sigfillset(&sigmask);
    sigdelset(&sigmask, SIGINT);

    sa.sa_handler = sig_handler;
    sigfillset(&sa.sa_mask);
    sigaction(SIGINT, &sa, NULL);
    
#ifdef HANDLER_THREAD
    /* only last thread will catch SIGINT */
    if (ctx->index == (THREADNUM - 1))
    {
        pthread_sigmask(SIG_SETMASK, &sigmask, NULL);
    }
#endif

    /* Just waiting for LIFETIME seconds */
    for (i = 0; i < LIFETIME; i++)
        sleep(1);

    return arg;
}

void sig_handler(int signum)
{
    if (signum == SIGINT)
    {
        printf("[HANDLER] I've got SIGINT\n");
    }
    return;
}



* Handling signal and waiting signal.

If handler is registered, it would be called when specified signal is sent. And we can also wait until the signal arrives.

int sigsuspend(const sigset_t *mask);

Function sigsuspend will block the program until specified signals in mask arrive.

#include <signal.h>
#include <stdio.h>
#include <string.h>

void handler(int signal)
{
    printf("[Handler] Signal handled\n");
    return;
}

int main()
{
    int sig;
    sigset_t set;
    struct sigaction sa;

    memset(&sa, 0, sizeof(struct sigaction));
    sa.sa_handler = handler;
    sigemptyset(&sa.sa_mask);
    sigaddset(&sa.sa_mask, SIGUSR1);

    sigaction(SIGUSR1, &sa, NULL);

    sigfillset(&set);
    sigdelset(&set, SIGUSR1);

    sig = sigsuspend(&set);

    printf("SIGUSR1 arrived\n");

    return 0;
}




* Signal and process

If a signal is sent to a certain process, it's also sent to all of its child as long as they have same process group id. That means, if a child become a process group leader, signal will not be propagated to it any more.

cf. session = process groups and processes, process group = processes, if pid == pgid, it's process group leader.

related functions : setpid, setpgid, setpgrp, getsid, setsid

> kill -SIGUSR1 -1234 # to send SIGUSR1 signal to all processes which have pgid as 1234

Tuesday, December 15, 2009

When you want to make your function manage data in a thread-safe way.

When we want to make thread-specific memory area, we can use pthread_setspecific()/getspecfic() like below.

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>

#define THREADNUM 30

struct ctx_t {
    pthread_t tid;
    int index;
} ctx[THREADNUM];

pthread_key_t t_key;

struct data_t {
    int var_a;
    char var_b;
};

void *thread_func(void *);
int sub_func(pthread_key_t);

int main()
{
    int i;
    void *res;

    pthread_key_create(&t_key, NULL);

    for (i = 0; i < THREADNUM; i++)
    {
        ctx[i].index = i;
        pthread_create(&ctx[i].tid, NULL, thread_func, (void *)&ctx[i]);
    }

    printf("Joining!\n");
    for (i = 0; i < THREADNUM; i++)
        pthread_join(ctx[i].tid, &res);

    pthread_key_delete(t_key);
}

void *thread_func(void *arg)
{
    int i;
    struct data_t *pdata;
    struct ctx_t *ctx;

    ctx = (struct ctx_t *)arg;

    pdata = (struct data_t *)malloc(sizeof(struct data_t));
    pdata->var_a = (ctx->index + 1) * 10;
    pdata->var_b = 0;
    pthread_setspecific(t_key, pdata);

    for (i = 0; i < 10; i++)
    {
        printf("Thread(%d) has value %d in its data space.\n",
            ctx->index, sub_func(t_key));
    }
    return NULL;
}

int sub_func(pthread_key_t key)
{
    struct data_t *pdata;
    pdata = (struct data_t *)pthread_getspecific(key);

    return pdata->var_a;
}

Monday, December 14, 2009

Barrier mechanism

While we make threads and make it work, sometime we want to make them stop at a certain point of work flow. Barrier can be used to make them wait till the other threads end their jobs. Here is an example. 5th thread will sleep 5 seconds and the other threads will wait with pthread_barrier_wait().

#include <pthread.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>

#define THREADNUM 5

pthread_barrier_t barrier;

struct thread_ctx {
    pthread_t tid;
    int idx;
} thr_ctx[THREADNUM];

void *start_thread(void *);
void clean_thread();
void exit_w_err(const char *);

int main()
{
    int i;

    pthread_barrier_init(&barrier, NULL, THREADNUM);

    for(i = 0; i < THREADNUM; i++) {
        thr_ctx[i].idx = i;
        if (pthread_create(&thr_ctx[i].tid, NULL,
            start_thread, (void *)&thr_ctx[i])) {
            exit_w_err("thread_create");
        }
    }
    clean_thread();
}

void *start_thread(void *in_ctx)
{
    int res;
    struct thread_ctx *ctx;

    ctx = (struct thread_ctx *)in_ctx;

    /*
     * thread will wait with barrier
     */
    if (ctx->idx == THREADNUM - 1) {
        sleep(5);
        printf("thread (%d) arrived\n", ctx->idx);
    }
    else 
        printf("thread (%d) started to wait\n", ctx->idx);

    if ((res = pthread_barrier_wait(&barrier)) == EINVAL)
    {
        printf("\t errno : %d\n", res);
        return NULL;
    }

    return in_ctx;
}

void clean_thread(void)
{
    int i;
    void *res; // dummy pointer.

    for (i = 0; i < THREADNUM; i++) {
        pthread_join(thr_ctx[i].tid, &res);
    }

    return;
}

void exit_w_err(const char *msg)
{
    fprintf(stderr, "[ERROR] %s\n", msg);
    exit(0);
}

Thursday, December 10, 2009

13 ways to make your web page faster.

1. Remove trivial text - comment, tab, space, etc.
2. Cache ajax data if it's possible.
3. Set your css, javascript out of the page and include them. So that it would be cached.
4. Script, css optimization.
5. Adjust ETags.
6. Trim meta data of your img files. If you want resize it, send them as resized.
7. Use gzip compression option so that compressed data will be transmitted.
8. Javascript DOM Access is slow. Cache them if it's possible.
9. Set your css on the top so that browser can render your page as it gets data.
10. Browser can't download javascript in parallel way. Put them on the bottom.
11. Don't use css expression, filter.
12. Set cache in response header field.
13. Note that some mobile devices cache only page smaller than 25kb.