프로그래밍/(도서)C#코딩의기술-실전

[C#] 코딩의기술(실전편) - 그 외, 챕터 별 간단 정리

Victory_HA 2022. 3. 31. 22:34

1.8 주석

  • 주석에는 아무거나 작성할 수 있습니다.
  • 컴파일 시 오류가 발생하지 않습니다.
  • 주석은 코드가 의미하는 바를 함축적으로 담고 있는 내용입니다.
  • 코드와 다른 내용의 주석이 존재할 수 있으니 관리를 해야합니다.

2.1 루프

  • 루프에서는 시작 조건과 종료 조건에 버그가 발생하기 쉽습니다.
  • 예제에서 i 값을 0과 1을 잘못 적거나 부등호에 등호(=)를 포함 여부에 따라 버그가 발생할 수 있습니다.
  • 컬렉션을 다루는 경우 foreach문을 사용하거나 컬렉션을 다루는 명령, API를 적극적으로 사용해야 합니다.
  • 예제*

for (int i = 0; i < 10; i++)
{
    Console.WriteLine($"{i}");
}

예외 발생

2.4 서식변경 과 중괄호

  • 코드 작성 시 명시적으로 그리고 일관성 있게 코드를 작성하는 것이 좋습니다.

중괄호 사용법 - C#

using System;

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello World.");
    }
}

중괄호 사용법 - GNU

using System;

class Program
{
    static void Main(string[] args)
        {
            Console.WriteLine("Hello World.");
        }
}

중괄호 사용법 - K&B

using System;

class Program
{
    static void Main(string[] args){
        Console.WriteLine("Hello World.");
    }
}

중괄호 사용법 - 원라이너(One-Liner)

using System;

class Program
{
    static void Main(string[] args){Console.WriteLine("Hello World.");}
}

2.8 조건문,선택문에서 값, 참조 형식의 차이

  • 아래 코드에서 매개변수가 int(값 형식), string(참조 형식) 형에 따라서 처리 속도에 차이가 있습니다.
  • 참조 형식은 참조 정보실제 데이터라는 두 가지 정보로 구성됩니다.
  • 값 형식은 실제 데이터 한 가지 정보로 구성됩니다.
  • 루프 안에서 조건문, 선택문을 사용하는 경우 속도를 고려하며 비교할 데이터의 형식을 정하는 것이 좋습니다.
  • class Program { private const int size = int.MaxValue; private static bool sub1 (int value1, int value2) { bool isTrue = false; for (int i = 0; i < size; i++) { isTrue |= value1 == value2; } return isTrue; } private static bool sub2(string value1, string value2) { bool isTrue = false; for (int i = 0; i < size; i++) { isTrue |= value1 == value2; } return isTrue; } static void Main(string[] args) { var start1 = DateTime.Now; sub1(123, 456); Console.WriteLine(DateTime.Now - start1); var start2 = DateTime.Now; sub2("123", "456"); Console.WriteLine(DateTime.Now - start2); } }

실행결과

2.10 예외를 처리 Try-Catch구문

  • Try-Catch구문으로 예외를 처리하는 할 수 있습니다.
  • 예외처리로 Throw 처리하는 것은 성능저하를 일으킵니다.
  • 예외처리 코드를 적용하면 프로그램을 정상 종료 시킬 수 있고, 적용하지 않으면 프로그램을 비정상 종료 시킬 수 있습니다.

2.12 다른 형에 대입하기

  • 형 변환하여 데이터 타입 변경이 가능합니다.
  • 형 변환 시 오버플로우를 조심해야합니다.
    • byte의 경우 최대값은 255이므로, 255이상의 int형 변수를 형 변환하는 경우 오버플로우가 발생합니다.
    • 형 변환*
      int a = 1;
      byte b = (byte) a    //형 변환       

2.13 아무것도 하지 않는 객체

  • null은 무효값입니다.
  • 디자인 패턴 관점에서는 아무것도 하지 않는 객체(NULL 객체)를 사용할 수 있습니다.
  • 아무것도 하지않는 클래스가 있다고 해서 마음대로 삭제해서는 안됩니다.
    • 아무것도 하지않는 클래스는 그 자체로 역할이 있습니다.

3.6 로컬 쿼리와 리모트 쿼리

해당 단원은 내용이 충분하지 않고, 이해하지 못해 책 내용을 간추려 작성했습니다.

  • 쿼리 식은 익명 메서드와 식 트리(expression tree) 2종류가 있습니다.
    Where(c => c>0);
  • 위 식을 배열에 사용하면 익명 메서드, 데이터베이스 쿼리에 사용하면 식 트리가 됩니다.
  • 원격 실행되는 쿼리 식은 로컬과 호환이 안 될 때가 있습니다.
  • 식 트리를 동적으로 구축하므로 식의 유연성을 높일 수 있습니다.

3.8 ToArray와 ToList

ToArray 메서드

  • 요소를 새로운 배열에 복사합니다.
  • 데이터 전체가 모이기 전까지는 코드가 진행되지 않습니다.
  • 배열의 요소를 변경할 수 있습니다.

ToList 메서드

  • 요소를 새로운 리스트에 복사합니다.
  • 요소를 늘리거나 줄일 수 있습니다.

3.9 Length, Count, Count()

  • 컬렉션 수를 확인하는 코드 중 Length, Count, Count메서드가 있습니다.
  • Length : 배열의 속성입니다.
  • Count : List클래스의 속성입니다.
  • Count메서드 : IEnumerable 인터페이스에서는 Count메서드만 사용할 수 있습니다.

3.10 애저(Azure)

  • Microsoft에서 만든 클라우드 컴퓨팅 플랫폼 및 인프라스트럭처 서비스입니다.
  • 애저에서 LINQ를 이용하여 쿼리를 실행할 수 있습니다.
  • 하지만 일부 사용할 수 없는 메서드도 있습니다.
    • ex> Skip(), Last(), LastOrDefault()

애저에서 사용할 수 있는 기능

  • Where
  • Take
  • First
  • FirstOrDefault
  • Select

지원하지 않는 기능 사용하는 방법

  • 데이터 양이 적을 때는 데이터를 배열에 넣어서 배열을 쿼리하는 방법이 있습니다.
  • 배열이 되면 쿼리 대상은 애저 저장소의 테이블이 아닌 배열로 바뀌므로 모든 메서드를 사용할 수 있습니다.
  • 데이터 양이 많은 경우, 쿼리를 2단계로 나눠서 사용하는 방법이 있습니다.
    • ex> 1단계 : 애저 저장소에서 쿼리하여 데이터 후보를 줄입니다.
    • 2단계 : 데이터를 배열로 바꾼 후 제약사항 없이 쿼리합니다.

3.11 LINQ의 WhereContain()

  • WhereContain은 지정한 항목의 값이 지정한 열거 객체에 있을 때 해당 값을 반환합니다.

사용예제

var datas = Query.Where(c => c.PartitionKey == pkey).WhereContain(null, "Rowkey", r);
var list = new List<T>();

foreach(var data in datas)
{
   list.AddRange(data);
}

4.2 자릿수 구하기

  • Math.Log10()은 지정한 숫자의 로그 값을 반환합니다.
    • 숫자 값을 Value넣으면 자릿수를 알 수 있습니다.
    • 자릿수를 반환하기 위해서는, 반환값이 소수이므로 한자리 올림해야합니다.
  • Math.Ceiling()은 소수점 자리를 올림하는 기능을 합니다.문제
  • 3,45,678,9012 라는 숫자가 있다. 각 숫자가 몇자릿수인지 쉽게 계산하고 싶다.

답안

static void Main(string[] args)
{
    int[] numbers = { 3, 45, 678, 9012 };
    foreach (var number in numbers)
    {
        Console.WriteLine("{0}의 자릿수는 {1}입니다.", number, Math.Ceiling(Math.Log10(number)));
    }
}

4.3 특정 단어 찾기

문제

  • TXT문서에서 특정 단어가 몇 번 등장하는지 확인하고 싶다.

답안

static void Main(string[] args)
{
    int count = 0;
    var file = File.OpenText(args[0]);
    for ( ; ; )
    {
        var line = file.ReadLine();
        if (line == null) break;
        if (line.Contains("오두막")) count++;
    }
    file.Close();
    Console.WriteLine($"등장 횟수는 {count}입니다.");
}
  • 위 코드에는 다음과 같은 문제가 있습니다.
    ```cs
  • line을 읽기 때문에 한 줄에 두 번 이상 단어가 있어도 1회로 카운트된다.
  • 검색 대상 단어가 코드에 포함돼 있어서 범용성이 낮다.
  • 예외 메시지의 정보가 충분하지 않다. 예외 발생가 발생할 수 있다.
  • 확실하게 파일을 닫도록 설계되지 않았다. 문제 발생 시 적절한 대응이 힘들 수 있다.

4.4 논리적인 코드 VS 논리적이지 않은 코드

문제

  • 정수를 임의의 컬렉션(값이 중복되지 않음)으로 받아서 짝수이자 소수인 값만 추출하고 추출한 것을 배열로 반환하는 메서드를 작성하자.

답안1

class Program
{
    private static bool isPrime(int n)
    {
        for (int i = 2; i < n; i++)
        {
            if (n / i * i != n)
                return false;
        }
        return true;
    }

    private static int[] evenPrime(IEnumerable<int> en)
    {
        var list = new List<int>();
        foreach (var item in en)
        {
            if (item % 2 == 0 && isPrime(item))
                list.Add(item);
        }
        return list.ToArray();
    }

    static void Main(string[] args)
    {
        int[] arr = Enumerable.Range(1, 100).ToArray();
        var r = evenPrime(arr);
        foreach (var item in r)
        {
            Console.WriteLine(item);
        }
    }
}

답안1 - 작성자의 의도

  • isPrime은 소수인지 판정합니다.
  • item % 2 == 0은 짝수 인지 판정합니다.
  • 결과를 배열로 반환하기 위하여 ToArray()를 사용합니다.

답안2

  • 답안1과 실행 결과는 동일하지만 코드가 훨씬 간결해집니다.
  • class Program { private static int[] evenPrime(IEnumerable<int> en) { return en.Any(c => c == 2) ? new int[] { 2 } : new int[0]; } static void Main(string[] args) { int[] arr = Enumerable.Range(1, 100).ToArray(); var r = evenPrime(arr); foreach (var item in r) { Console.WriteLine(item); } } }

답안2 - 작성자의 의도

  • 소수이면서 짝수가 되는 값은 2밖에 없습니다.
  • 값이 중복되지 않는다는 것이 전제이므로 결과는 크기가 0인 배열
  • 또는 크기가 1이고 요소 값이 2인 배열이어야합니다.
  • 다시말해, 후보가 2밖에 없으니 2를 포함하는지만 확인하는 코드입니다.

정리

  • 절대 답에 포함될 수 없는 수를 판정하기 위해서 긴 코드를 작성하는 것은 무의미합니다.
  • 정답에 가깝게 코드를 간결하게 작성할 수 있어야 합니다.

4.5 1~10까지 더한 값은?

문제

  • 1,2,3,4,5,6,7,8,9,10을 모두 더한 값을 알고싶다.

답안1

  • 단순하고, 다음과 같이 작성하면 원치 않는 결과가 출력될 수 있습니다.
    Console.WriteLine(1+2+3+4+5+6+7+8+9+10);

답안2

  • 간결하고, 버그가 발생하기 어렵습니다.
    Console.WriteLine((1+10)*5);

4.6 소통이 원활한 팀

  • 세가지 기능을 세명이 한팀으로 작성하는 경우 소통이 원활해야합니다.
  • 소통이 원활하지 않으면 목적에 맞지 않는 코드가 작성될 수 있습니다.
  • 소통이 원활하지 않으면 버그가 발생하는 코드가 작성될 수 있습니다.

4.7 긴 코드 VS 짧은 코드

  • 장황한 코드는 유지보수가 힘들 수 있고 가독성이 떨어질 수 있습니다.
  • 짧지만 아무도 이해할 수 없는 어려운 코드는 이해하기 어렵고, 몰래 코드를 바꿔도 그게 버그인지 모를 수 있습니다.
  • 이해하기 쉽고 간결한 코드를 작성하도록 노력해야합니다.

4.8 심리적으로 안정적인 코드

  • 이해하고 간결한 코드를 작성해야합니다.
  • 코드가 정상 동작하는지 확인하는 것보다 가독성에만 신경쓰면 안됩니다.

4.9 의존성 분리

  • 의존성으로 인해 발생할 수 있는 문제점들은 실제로 문제점들이 드러난 후에는 되돌릴기가 어려울 수 있습니다.
  • 개발 시작 단계에서 의존성을 관리하고, 지속적으로 관심을 두어 문제가 발생하지 않도록 해야 합니다.

의존성 주입(dependency injection)

  • 의존성 주입은 하나의 객체가 다른 객체의 의존성을 제공하는 테크닉입니다.
  • 의존성 주입의 의도는 객체의 생성과 사용의 관심을 분리하는 것입니다.
  • 이는 코드의 가독성과, 재사용률을 높여줍니다.

4.10 캡처 실수

  • 아래 예제에서 변수 i는 3이라는 값이 들어있는 유일한 변수입니다.
  • iCopy는 람다 식에서 캡처하여 0이 있는 iCopy,1이 있는 iCopy,2이 있는 iCopy가 있습니다.
  • 그래서 캡처를 그대로 사용하는 경우 3을 세번 출력하지만,
  • 캡처를 복사하는 경우 0, 1, 2를 출력합니다.

캡처를 그대로 사용하는 코드

static void Main(string[] args)
{
    var arr = new Action[3];
    for (int i = 0; i < arr.Length; i++)
    {
        arr[i] = () =>
        {
            Console.WriteLine(i);
        };
    }

    foreach (var item in arr)
    {
        item();
    }
}

실행결과

3
3
3

캡처를 복사해서 사용하는 코드

  • iCopy에 변수를 복사 후 사용합니다.
static void Main(string[] args)
{
    var arr = new Action[3];
    for (int i = 0; i < arr.Length; i++)
    {
        // 캡처를 복사해서 사용
        var iCopy = i;
        arr[i] = () =>
        {
            Console.WriteLine(iCopy);
        };
    }

    foreach (var item in arr)
    {
        item();
    }
}

실행결과

0
1
2

4.11 지역 변수의 좀비화

  • 변수를 캡처하는 코드에 size값이 100 -> 10000000 으로 바뀌면 Out Of Memory 예외가 발생합니다.
  • 필요없는 배열이 메모리에 할당 된 후 해제되지 않기 때문입니다.
  • 캡처보다 변수를 인수로 전달하는 코드를 지향하도록 해야합니다.변수를 캡처하는 코드
  • 캡처하는 부분 n[0] = n[1];은 언제 실행될지 모르므로, 참조하는 변수 n의 수명은 메서드가 종료되더라도 끝나지 않습니다.
  • 즉 변수의 좀비화가 발생합니다.
    static void Main(string[] args)
    {
      const int size = 100;
      var arr = new Action[size];
      for (int i = 0; i < arr.Length; i++)
      {
          int[] n = new int[size];
          arr[i] = () =>
          {
              n[0] = n[1];        // 변수 캡처
          };
          arr[i]();
      }
    }

변수를 인수로 전달하는 코드

  • 캡처하는 부분이 없으므로 지연처리도 되지 않고 변수의 좀비화도 발생하지 않습니다.
    static void Main(string[] args)
    {
      const int size = 100;
      var arr = new Action<int[]>[size];
      for (int i = 0; i < arr.Length; i++)
      {
          int[] n = new int[size];
          arr[i] = (p) =>
          {
              p[0] = p[1];        // 람다식이 인수를 받음
          };
          arr[i](n);
      }
    }

4.12 전용 DLL

  • 전용 DLL은 필요하다. VS 전용 DLL은 필요하지 않다. 두 가지 의견 모두 일리가 있습니다.

전용 DLL은 필요하다

  • 전체 프로그램에서 필요한 기능이 아닌 특정 기능에서만 사용되는 기능들을 하나의 DLL로 모아서 사용하는 경우 편리하게 사용할 수 있습니다.
  • Internal을 사용할 수 있습니다. private는 사용 범위가 너무 작고, public은 너무 넓은 경우 Internal을 사용하면 편리합니다.

전용 DLL은 필요가 없다

  • 프로그램 실행시 DLL이 많으면, DLL 한 개라도 없으면 프로그램 동작 시 오류가 발생하므로 필요하지 않다.

4.13 고정 쿼리 VS 동적 쿼리

  • 동적,고정 쿼리의 이해가 선행 되어야 할 것 같습니다.
  • 문제
  • 변수 flag가 1 이상인 요소를 추출하고 false이면 짝수 요소를 출력한다.

답안1 - 고정 쿼리 식

  • 조건에 따라 Where() 메서드를 사용하여 조건에 따라 쿼리하는 식입니다.
static void Main(string[] args)
{
    bool[] arr = { true, false };
    int[] data = { 0, 1, 2 };
    foreach (var flag in arr)
    {
        int[] r;
        if (flag)
            r = data.Where(x => x >= 1).ToArray();
        else
            r = data.Where(x => x % 2 == 0).ToArray();

        Console.WriteLine($"flag : {flag}");

        foreach (var item in r)
        {
            Console.WriteLine($"{item}");
        }
    }
}

실행결과

동적 쿼리 식

  • Where()메서드를 한번만 사용합니다.
  • 조건이 복잡해 질수록 방법에 따라서 동적 쿼리 식으로 작성하는 코드가 더 짧아질 수 있습니다.
  • 아래 예제에서 Compile()메서드가 있는데, 왜 사용했는지는 함께 고민해봐야 할 예제입니다.
static void Main(string[] args)
{
    bool[] arr = { true, false };
    int[] data = { 0, 1, 2 };
    foreach (var flag in arr)
    {
        ParameterExpression cParam = Expression.Parameter(typeof(int), "c");
        BinaryExpression filter;

        if (flag)
            filter = Expression.GreaterThanOrEqual(cParam, Expression.Constant(1));
        else
        {
            var calc = Expression.Modulo(cParam, Expression.Constant(2));
            filter = Expression.Equal(calc, Expression.Constant(0));
        }
        var lambda = Expression.Lambda<Func<int, bool>>(filter, cParam).Compile();

        int[] r;
        r = data.Where(lambda).ToArray();

        Console.WriteLine($"flag = {flag}");

        foreach (var item in r)
        {
            Console.WriteLine($"{item}");
        }
    }
}

실행결과

  • PrameterExpression
  • Expression.Parameter
  • Expression.GreaterThanOrEqual
  • Expression.Modulo
  • Expression.Equal
  • Expression.Lambda

4.14 실행하기 전에 알수있는 식

실행하기 전에 알 수 있는 식

  • 주어진 쿼리 이외는 절대로 실행되지 않도록 작성한 코드입니다.
  • 쿼리가 고정되어 있으므로 악용될 위험도 적고, 결과 예측이 쉽습니다.
  • 하지만 코드가 장황해 질 수 있습니다.
class Triple
{
    public int x, y, z;
}
class Program
{
    private static void sub1(IEnumerable<Triple> en)
    {
        foreach (var item in en.Where(c => c.x == 0))
        {
            Console.WriteLine($"{item.x},{item.y},{item.z}");
        }
    }

    private static void sub2(IEnumerable<Triple> en)
    {
        foreach (var item in en.Where(c => c.y == 0))
        {
            Console.WriteLine($"{item.x},{item.y},{item.z}");
        }
    }
    private static void sub3(IEnumerable<Triple> en)
    {
        foreach (var item in en.Where(c => c.z == 0))
        {
            Console.WriteLine($"{item.x},{item.y},{item.z}");
        }
    }

    static void Main(string[] args)
    {
        Triple[] t =
        {
            new Triple(){x=0,y=1,z=2},
            new Triple(){x=1,y=0,z=2},
            new Triple(){x=2,y=1,z=0}
        };

        Console.WriteLine("case1:");
        sub1(t);
        Console.WriteLine("case2:");
        sub2(t);
        Console.WriteLine("case3:");
        sub3(t);
    }
}

실행결과

실행하기 전에 알 수 없는 식

  • sub1(),sub2(),sub3()처럼 여러개의 메서드 한 개 메서드로 구현했습니다.
  • 판정 식이 매개변수로 입력되기 때문에 추가 메서드 구현은 필요없습니다.
class Triple
{
    public int x, y, z;
}
class Program
{
    private static void sub(Func <Triple, bool> cond, IEnumerable<Triple> en)
    {
        foreach (var item in en.Where(c=>cond(c)))
        {
            Console.WriteLine($"{item.x},{item.y},{item.z}");
        }
    }

    static void Main(string[] args)
    {
        Triple[] t =
        {
            new Triple(){x=0,y=1,z=2},
            new Triple(){x=1,y=0,z=2},
            new Triple(){x=2,y=1,z=0}
        };

        Console.WriteLine("case1:");
        sub(c => c.x == 0, t);
        Console.WriteLine("case2:");
        sub(c => c.y == 0, t);
        Console.WriteLine("case3:");
        sub(c => c.z == 0, t);
    }
}

실행결과

4.15 위임형(Delegate)

Delegate란?

  • 델리게이트는 메소드를 참조하는 데이터 형식입니다.
  • 클래스의 정적 메소드와 클래스의 특정 인스턴스의 메소드를 호출하는데 사용됩니다.
  • 델리게이트를 사용하게 되면 실행시 어떤 메소드를 호출하고자 하는지 결정할 수 있습니다.

사용예제

public class Program{
    public delegate void MyDelegate(string msg);

    static void HelloPrintStatic(string msg){
        Console.WriteLine(msg + " Hello World!");
    }

    public void HelloPrintMember(string msg){
        Console.WriteLine(msg + " Hello World!")
    }
    public static void Main(){
        MyDelegate delegateHelloStatic = Program.HelloPrintStatic;
        delegateHelloStatic("Static");
        Program pro = new Program();
        MyDelegate delegateHelloMember = pro.HelloPrintMember;
        delegateHelloMember("Member");
    }
}

5.1 스토리지의 두 가지 키

참조 : Table Storage 소개 - Azure의 개체 스토리지 | Microsoft Docs

애저 (Azure)

  • Microsoft 에서 제공하는 클라우드 서비스

Azure Storage 서비스

  • Azure Storage 플랫폼은 최신 데이터 스토리지 시나리오를 위한 Microsoft의 클라우드 스토리지 솔루션입니다.

Table Storage

  • Azure Table Storage는 비관계형 구조화된 데이터(구조화된 NoSQL 데이터라고도 함)를 저장하는 서비스로, 스키마 없이 디자인된 키/특성(value) 저장소를 제공합니다.

Table Storage의 두가지 키

PartitionKey

  • 테이블은 저장소 노드에서 부하 분산을 지원하기 위해 분할으로 분할됩니다.
  • 테이블의 엔터티는 분할별로 구성됩니다. 분할은 동일한 분할 키 값을 가지는 일련의 엔터티입니다.
  • 분할 키는 PartitionKey 속성으로 지정되며, 특정 테이블 내에서 분할에 대한 고유 식별자로 사용됩니다.
  • 분할 키는 엔터티 기본 키의 첫 번째 부분을 구성합니다. 파티션 키는 최대 1 KiB 크기의 문자열 값일 수 있습니다.
  • 모든 삽입, 업데이트 및 삭제 작업에 PartitionKey 속성이 포함되어야 합니다.

RowKey

  • 기본 키의 두 번째 부분은 RowKey 속성으로 지정되는 행 키입니다.
  • 행 키는 주어진 파티션 내 엔터티의 고유 식별자입니다.
  • PartitionKeyRowKey는 함께 테이블 내의 모든 엔터티를 고유하게 식별합니다.
  • 행 키는 크기가 최대 1 KiB 수 있는 문자열 값입니다.
  • 모든 삽입, 업데이트 및 삭제 작업에 RowKey 속성이 포함되어야 합니다.

5.2 애저 스토리지는 미로?

애저 스토리지는 느리다?

  • 아래 코드에서 foreach문 안에 매번 ToArray()가 호출되면서 쿼리 실행 속도가 느려집니다.
    • ToArray()를 foreach문 밖으로 빼내어 쿼리 실행 속도를 줄이는 방법이 있습니다.
  • 쿼리 한 번으로 가능한 많은 결과를 얻을 수 있도록 식을 만들어야 하고, 쿼리 횟수를 최대한 줄인다면 처리 속도가 빨라집니다.

예제코드

foreach(...)
{
    var picsCount = table.tbl.CreateQuery<T>().Take(1).ToArray();
}

5.3 애저 테이블의 전방 일치

전방 일치란,

자연어의 단어를 검색 용어로 하여 검색을 행할 경우의 일치 판단 기법의 하나입니다.

데이터 검색 시 키로서 주어진 문자열과 검색 대상의 문자열의 선두로부터의 길이가 일치하는 데이터를 확인하는 방법입니다.

유니코드(Unicode) 전방 일치 검색 하는 방법

  • 유니코드의 상한 값인 \uFFFF를 사용하여 전방 일치 검색이 가능합니다.
  • 유니코드가 아닌 언어는 사용하기 어려울 수 있습니다.

5.4 애저 테이블의 복수 조건

  • 쿼리 한 번에 사용할 수 있는 식의 길이가 제한되어 있습니다.
  • 쿼리 식이 너무 길고, 조건이 복잡한 경우 쿼리 실행이 안될 수 있습니다.
  • 여러가지 조건이 포함된 식 작성 시, 아래 내용을 참고하여 최적화 된 식을 작성할 수 있습니다.

부등호를 사용하자

  • A,B,C 데이터 값의 비교를 사용하는 경우 부등호를 사용합니다.
  • C >= A && B < A

불필요한 데이터는 나중에 제거

  • 불필요한 데이터 전송이 발생하지만, 제외할 데이터 양이 적다면 나중에 제거하는 것이 방법이 될 수 있습니다.
    • 예를 들어, 0 ~ 100까지 데이터에서 50을 제외하고 싶다면, 0 ~ 100 데이터를 획득 후 50을 제외하는 방법이 있습니다.

식을 동적으로 만들자

  • 쿼리 식을 최적화 해서 동적으로 구축하여 복잡한 조건을 처리하는 식을 만들 수 있습니다.
  • 동적으로 작성하는 경우 긴 식을 분할할 수 있습니다.

6.1 버그 수정

  • 개발 과정에서 버그가 발생할 수 있습니다.
  • 고객의 요구사항과 버그의 원인을 생각하면서 개발을 해야합니다.

6.2 분명하지만 활용할 수 없는 조언

  • 버그를 수정하는 주니어 개발자에게 지나친 조언은 민폐가 될 수 있습니다.
    • 예를들어, "함수형 언어는 변수를 변경하는 처리가 없어서 버그도 자주 발생되지 않는다. 그러므로, 함수형 언어를 배워서 버그를 수정하도록 해야한다. "
    • 위와 같은 조언은 옳은 말이지만, 버그를 당장 수정해야하는 주니어 개발자의 입장에선 잘못된 조언이 될 수 있습니다.

6.3 한 글자라도 변경하면 처음부터 다시 테스트?

  • 인터넷 기술에 정통하다고 자부하는 사람들이 많이 있습니다.
  • 하지만 이들은 테스트의 중요성을 종종 간과합니다.
  • 테스트는 사실 매우 지루한 작업입니다.
  • 단순 작업을 반복해야하고, 한 글자라도 변경되면 처음부터 다시 테스트해야 합니다.
  • 하지만 작업에는 고도의 전문성이 요구됩니다.
  • 프로가 세운 엄격한 테스트 계획은 모든 기능을 망라하지만, 초보자는 정해진 패턴만 테스트하므로 사용하지않는 기능은 무의식적으로 테스트되지 않을 수 있습니다.

6.4 동작한다는 것은 중간 결과물일 뿐이다

  • 아래 upper 메서드는 소문자를 대문자로 변경하는 메서드입니다.
  • 알파벳 몇개를 테스트를 할 땐 문제가 없어보였지만, upper메서드에는 오류가 있습니다.
    • z 에 대해서 대문자 변경이 되지 않습니다.
  • 기능이 동작한다는 것은 완료됐다 가 아닙니다.
  • 테스트를 진행하여 기능에 대한 보증이 필요합니다.

예제

Class program
{
    private static cahr upper(char word)
    {
        if(word >= 'a' && word < 'z')
            word -= (char)0x20;
        return word;
    }
    static void Main(string[] args)
    {
        Console.WriteLine(upper('a'));
    }
}

6.5 페일 세이프 개념

  • 일반적으로 문제가 발생하면 더 안전한 쪽으로 피하는 것이 바람직하다. 라는게 페일 세이프 개념입니다.
  • string name; 선언만 하는 경우 초기화 하지 않은 변수를 사용하고 있습니다.라는 경고 메시지가 발생할 수 있습니다.
  • 예외를 피하고자 예제에서 name에 null을 초기화 했습니다.
  • 하지만 null 참조 예외가 발생할 수 있기 때문에 옳은 방법이 아닙니다.
  • 값을 명확히 기록했다면, 초기화하지 않은 변수에 대한 경고메시지는 발생하지 않습니다.
  • null로 초기화하지 말고 경고 메시지가 발생하는 원인을 찾아서 해결해야합니다.

예제

enum Sex { Male, Female}
class Program
{
    private static string sub(Sex sex)
    {
        //string name = null;        //예외가 발생할 수 있는 코드
        string name;
        if (sex == Sex.Male)
            name = "Male";
        else
            name = "Female";
        return name;
    }
    static void Main(string[] args)
    {
        Console.WriteLine(sub(Sex.Male).ToUpper());
        Console.WriteLine(sub(Sex.Female).ToUpper());
    }
}

6.6 부분 테스트 VS 전체 테스트

제품 사양서

 1부터 10까지 정수를 배열로 작성한다.
 배열의 평균값을 구한다.
 결과는 5이다.

작성된 코드

private static int[] gen()
{
    return Enumerable.Range(1, 10).ToArray();
}
private static double ave(int[] ar)
{
    return ar.Average();
}
static void Main(string[] args)
{
    Console.WriteLine(ave(gen()));
}

실행결과

5.5
  • 작성된 코드 결과와 제품 사양서에 작성된 결과 값이 다릅니다.
  • 실제로 1~10 까지 수동으로 결과 값을 계산해보면 5.5가 나옵니다.
    • (1+2+3+4+5+6+7+8+9+10) /10 = 5.5
  • 즉, 제품 사양서가 버그를 품고 있을 가능성도 있습니다.
  • 테스트는 코드 뿐만이 아니라 제품 사양서까지 전체적으로 하지 않으면 의미가 없습니다.
  • 일부만 테스트하면 품질을 높이는 데는 도움이 될지 몰라도 위와 같은 문제가 발생할 수 있습니다.

6.7 웹 시스템과 테스트 문제

  • 해당 부분은 웹과 관련된 테스트 내용을 포함하므로 제외합니다.

6.8 GUI와 테스트

  • 테스트란 버그가 발생하고 나서 하는 것이 아닙니다.
  • 처음 단계부터 테스트를 의식하면 코드를 작성해야합니다.
  • 테스트를 의식한 설계가 중요합니다.
  • 개발에 있어서 테스트는 필수으로 해야합니다.테스트하기 어려운 패턴
  • GUI 애플리케이션은 테스트와 궁합이 잘 맞지 않습니다.

XML파일 일부

<Button Click="Button_Click"> calc it </Button>

C#파일 일부

private void Button_Click(object sender, RoutedEventArgs e)
{
    var n = 1 + 2;
    MessageBox.Show(n.ToString());
}

테스트하기 쉬운 패턴

  • 단순한 클래스는 테스트하기 쉽습니다.

C#파일 일부1

private void Button_Click(object sender, RoutedEventArgs e)
{
    var n = 1 + 2;
    MessageBox.Show(n.ToString());
}

C#파일 일부2

class Class1
{
    public static int Calc()
    {
        return 1 + 2;
    }
}

6.9 몽키 테스트는 의미가 있는가?

몽키테스트

  • 원숭이처럼 마구잡이로 키를 누르는 테스트입니다.
  • 어떤 장치건 아무렇게나 조작하다 보면 생각하지 못한 버그를 발견할 수 있습니다.몽키테스트가 유용한 점
  • 보통 때보다 빠르게 조작하는 행위 자체가 예상 외의 동작을 발생시키기 때문에 버그를 찾을 수 있습니다.

6.10 목(MOCK)을 사용하는 의미가 있는가?

목(MOCK)의 유용한 점

  • 치명적인 변경이 포함된 기능은 목을 사용하지 않고서는 테스트할 수 없습니다.목(MOCK)의 한계
  • 목은 대상 객체의 동작을 모방하지만 완전한 모방은 불가능합니다.
  • 보통은 테스트에 필요한 충분한 기능을 제공하지만, 정확도가 높은 기능까지 모방하기 어렵습니다.

6.11 테스트를 전제로 한 설계 방법

  • 테스트 상황을 고려해서 코드를 작성하다 보면 불필요한 코드가 포함될 수 있습니다.
  • 하지만 테스트는 필수적인 요소이기 때문에 이상 케이스에 대한 고려, 오류 발생 시의 복원, 테스트 상태 등으로 코드의 변질 우려 등을 생각하며 코드 작성이 필요합니다.

6.12 마침표와 쉼표의 차이

  • Console.WriteLine()은 가변 길이 인수이므로 인수가 많든 적든 오류가 발생하지 않습니다.
  • 인수가 부족할 때는 오류가 발생하지만, 인수가 많아지면 오류가 발생하지 않습니다.
  • 아래 예제와 같은 상황에서 1.5 대신 1,5를 인수로 넣는다면 의도치 않은 결과 값이 반환되므로 주의해야합니다.

예제

public class Program
{
    static void Main(string[] agrs)
    {
        Console.WriteLine("{0}",1,5);
    }
}

의도한 결과

1.5

실제 결과

1