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 스토리지의 두 가지 키
애저 (Azure)
- Microsoft 에서 제공하는 클라우드 서비스
Azure Storage 서비스
- Azure Storage 플랫폼은 최신 데이터 스토리지 시나리오를 위한 Microsoft의 클라우드 스토리지 솔루션입니다.
Table Storage
- Azure Table Storage는 비관계형 구조화된 데이터(구조화된 NoSQL 데이터라고도 함)를 저장하는 서비스로, 스키마 없이 디자인된 키/특성(value) 저장소를 제공합니다.
Table Storage의 두가지 키
PartitionKey
- 테이블은 저장소 노드에서 부하 분산을 지원하기 위해 분할으로 분할됩니다.
- 테이블의 엔터티는 분할별로 구성됩니다. 분할은 동일한 분할 키 값을 가지는 일련의 엔터티입니다.
- 분할 키는
PartitionKey
속성으로 지정되며, 특정 테이블 내에서 분할에 대한 고유 식별자로 사용됩니다. - 분할 키는 엔터티 기본 키의 첫 번째 부분을 구성합니다. 파티션 키는 최대 1 KiB 크기의 문자열 값일 수 있습니다.
- 모든 삽입, 업데이트 및 삭제 작업에
PartitionKey
속성이 포함되어야 합니다.
RowKey
- 기본 키의 두 번째 부분은
RowKey
속성으로 지정되는 행 키입니다. - 행 키는 주어진 파티션 내 엔터티의 고유 식별자입니다.
PartitionKey
및RowKey
는 함께 테이블 내의 모든 엔터티를 고유하게 식별합니다.- 행 키는 크기가 최대 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
'프로그래밍 > (도서)C#코딩의기술-실전' 카테고리의 다른 글
[C#] 코딩의기술(실전편) - 4.1 var VS object VS dynamic (0) | 2022.03.31 |
---|---|
[C#] 코딩의기술(실전편) - 3.13 열거 데이터의 순서 변경 (0) | 2022.03.31 |
[C#] 코딩의기술(실전편) - 3.12 First메서드 VS Take메서드 (0) | 2022.03.31 |
[C#] 코딩의기술(실전편) - 3.7 Cast VS OfType (0) | 2022.03.31 |
[C#] 코딩의기술(실전편) - 3.5 짧은쿼리 VS 긴 쿼리 (0) | 2022.03.31 |