카테고리 없음

프로그래밍 공부 정리

parksungmin0221 2025. 5. 21. 23:27

오늘은 이번주에 공부한 것을 정리해 보겠습니다.

 

git은 소스 코드의 변경 이력을 관리하고, 여러 개발자가 동시에 협업할 수 있도록 도와주는 분산 버전 관리 시스템(DVCS)입니다.

즉, 소프트웨어 개발에서 코드를 관리하고 기록하고 버전관리를 해주므로 체계적인 개발이 가능하도록 도와주는 소프트웨어입니다.

현재는 오픈소스 프로젝트로부터 대기업의 프로젝트까지 매우 널리 사용이 되고 있습니다.

 

git의 구조와 주요 개념

분산형 구조: 모든 사용자가 전체 프로젝트의 이력을 로컬에 가지고 있으며, 인터넷이 연결되지 않은 상황에서도 작업이 가능합니다.

커밋(commit): 각 변경 사항을 저장하는 단위입니다. 커밋에는 변경된 파일과 메시지, 그리고 작성자의 정보가 기록됩니다.

브랜치(branch): 여러명이 동시에 작업할 수 있게 해주는 가지 입니다. 예를 들어 새로운 기능을 개발할 때는 feature/기능명 같은 브랜치를 따서 작업을 하게 됩니다. 독립적인 공간을 만듭니다.

병합(merge)과 충돌(conflict): 여러 브랜치에서 작업한 내용을 하나로 합치는 과정을 병합이라고 하고, 같은 부분을 서로 다르게 수정했다면 충돌이 발생합니다.

원격 저장소(remote): 로컬 저장소와 별개로, 중앙 서버에 저장된 저장소를 의미합니다.

 

git의 사용법

로컬 저장소 생성 및 초기화 코드

git init

 

원격 저장소 연결 코드

git clone [저장소 URL]

 

파일 변경 사항 관리 코드

git status // 현재 상태 확인
git add . // 모든 변경 파일 스테이싱
hit commit -m '메시지' // 변경 내용 커밋

 

브랜치 관리 코드

git branch // 브랜치 목록 보기
git checkout -b feature/new-feature // 새 브랜치 생성 및 이동
git merge feature/new-feature // 브랜치 병합

 

git이 중요한 이유

1. 이력관리: 누가 언제 어떤 파일을 어떻게 수정했는지 명확하게 알 수 있습니다.

2. 백업: 로컬과 원격에 모두 저장이 되서 데이터의 유실 위험이 적습니다.

3. 실험 및 롤백: 새로운 기능을 실험하고, 문제가 있으면 이전 버전으로 쉽게 돌아갈 수 있습니다.

4. 코드 리뷰와 PR(Pull Request): 다른 개발자가 작업한 내용을 리뷰하고, 병합 절차를 거쳐 품질을 높일 수가 있습니다.

 

결론

git은 실수를 하거나 오류가 나면 쉽게 취소할 수 있다.

과거의 원하는 어느 시점으로 돌아갈 수 있다.

과거의 코드와 현재의 코드를 비교해 볼 수있다.

git은 형상관리 도구라고도 불립니다.

 

하지만 git은 로컬 저장소를 사용합니다. 그래서 다른 개발자와 실시간으로 작업을 공유할 수 없습니다.

여기서 등장한게 바로 github입니다.

 

github란

단순한 원격 저장소를 넘어서, 개발자들이 코드를 공유하고 협력할 수 있는 플랫폼입니다.

 

git과 github의 차이점

git은 remote 저장소를 지원한 하는 형상관리 도구입니다.

github는 바로 깃에서 지원하는 remote 저장소 입니다. 

 

Linux의 디렉리와 파일, 파일 경로

리눅스는 모든 것을 파일로 취급합니다. 하드웨어 장치, 디렉토리 심지어 프로세스까지도 파일로 표현을 합니다. 그래서 리눅스의 파일 시스템은 루트 디렉토리를 기준으로 트리 구조를 가집니다. 리눅스는 항상 시스템 전체의 단 하나의 트리만을 가집니다.

 

리눅스의 디렉토리 구조는 FHS(Filesystem Hierarchy Standard)라는 표준 사양을 따릅니다.

 

/bin: 일반 사용자 및 관리자가 사용하는 명령어의 실행파일이 배치되어있는 디렉토리입니다. 시스템관 관련된 중요도가 높은 명령어를 가지고 있습니다.

/dev: 디스크, 키보드 등 하드웨어를 다루기위한 파일인 디바이스 파일이 배치되어 있는 디렉토리입니다.

/etc: 리눅스에서 돌아가는 어플리케이션의 설정 파일 및 리눅스 자체의 설정파일 등이 배치됩니다. 운영 및 관리에 중요한 디렉토리 입니다.

/home: 사용자별로 할당되는 개인용 디렉토리인 홈 디렉토리가 배치되는 디렉토리입니다. 사용자 이름으로 디렉토리 명이 사용됩니다.

/sbin: /bin과 비슷하게 실행 파일을 포함하는 디렉토리입니다. 관리자용 명령어도 포함되어 있습니다.

/tmp: 임시 파일이 들어있는 디렉토리입니다. 어플리케이션 실행 중 임시로 작업 결과를 파일로 보존할 때 이 디렉토리에 저장합니다. 정기적으로 파일을 삭제합니다.

/usr: 설치한 어플리케이션의 실ㄹ행파일, 문서, 라이브러리 등이 이 디렉토리에 포합됩니다. 루트디렉토리와 구조가 비슷합니다.

/var: 가변적인(variable) 데이터를 저장하기 위한 디렉토리입니다. 어플리케이션 실행 중 만들어진 데이터, 로그, 메일 등 이곳에 저장이 됩니다.

 

경로를 표시 할 때는 / 를 사용합니다. 경로중에서도 절대 경로와 상대 경로가 존재합니다.

절대 경로: 루트 디렉토리부터 해당 파일까지의 경로를 표시하는 것입니다. 예로 /home/gymin 이 있습니다.

상대 경로: 현재 디렉토리를 기준으로 표기하는 경로입니다.

 

이제 경로를 표시하는 명령어도 있습니다.

pwd: 현재 디렉토리 경로를 출력합니다. 만약에 로그인하면 홈 디렉토리에서 시작합니다.

cd: 디렉토리 이동를 하는 명령어 입니다. 

ls: 디렉토리 안의 파일을 출력하는 명령어 입니다. *과 ?를 사용하면 경로를 확장 할 수 있습니다.

*: 임의의 문자열    ?: 임의의 한 문자

- 으로 옵션을 지정할 수 있습니다.

여기서 경로관련해 특수 기호가 존재합니다.

. : 현재 디렉토리를 표현합니다.

.. : 상위 디렉토리를 표현합니다.

~: 현재 사용자의 홈 디렉토리를 의미합니다.

 

만약 drwxr-xr-x 2 root root 36864 Sep 13 05:42 bin 이라는 파일이 있으면

d: 파일 타입입니다.(디렉토리)

rwxr-xr-x: 파일 모드입니다.

2: 링크 수입니다.

root: 소유자입니다.

root: 소유그룹입니다.

36864: 파일 크기입니다.

Sep 13 05:42: 티임스탬프 입니다.

bin: 파일 이름입니다.

 

리눅스에서는 디렉토리나 파일을 만들 수도 있습니다.

mkdir: 디렉토리 생성하는 명령어입니다.

touch: 파일을 생성하는 명령어입니다.

rm: 파일이나 디렉토리를 삭제합니다.

cp: 파일을 복사하는 명령어입니다.

mv: 파일을 이동하는 명령어 입니다.

 

해시알고리즘 이라는 것도 있습니다. 

해시알고리즘이란(Hash Algorithm)

데이터를 빠르게 저장하고 검색하기 위한 알고리즘으로 키를 사용해 데이터값을 조회하는 방법을 의미 합니다. 

이러한 키-값을 해시테이블이라는 구조에 저장하여 키에 매핑되는 인덱스 값을 빠르게 찾을 수가 있습니다.

 

키: 문자열 혹은 정수 형태로 구성된 키이며ㅑ key-value 형태로 실제 데이터 구조를 가지고 있습니다.

해시함수: 전달받은 키 값을 통해서 해시함수가 수행됩니다. 이 해시 함수 내에서는 키를 고유한 해시값으로 변환합니다.

해시테이블: 해시 함수로 전달 받은 고유한 해시값을 기반으로 해시 인덱스를 결정하고 해시값은 실제 값이 들어가게 됩니다.

 

해시알고리즘의 특징

1. 고정된 길이의 출력합니다.

2. 빠른 계산 속도를 가집니다.

3. 단방향성(비가역성)입니다.

4. 충돌 회피성도 있습니다.

5. 작은 변화에 민감합니다.

 

대표적인 해시알고리즘

MD5: 128비트 해시값을 생성합니다. 속도는 빠르지만 보안이 취약합니다

SHA-1: 160비트의 해시값을 생성합니다. 현재는 충돌이 발견되어서 안전하지 않습니다.

SHA-256, SHA-512: 각각 256비트, 512비트 해시값을 생성합니다. 이 알고리즘이 현자는 충분히 안전하다고 판단이 되어 많이 사용합니다.

SHA-3: SHA-2의 후속 알고리즘으로, 구조가 다릅니다.

 

해시알고리즘의 활용도

1. 비밀번호 관리

2. 데이터 무결성 검증

3. 디지털 서명

4. 블록체인

5. 해시테이블

6. 전자 서명/ 공인인증

 

하지만 이런 해시알고리즘에도 한계점이 존재합니다.

충돌: 해시값의 범위는 한정되어 있어 이론적으로 서로 다른 입력값이 같은 해시값을 가질 수 있습니다. 그래서 해시함수는 이러한 충돌을 줄여이는 것이 목표입니다.

무차별 대입 공격: 해시값만 가지고 원본을 찾는 것은 어렵지만 짧거나 단순한 입력값의 경우 사전공격(Dictionary Attack), 레인보우 테이블 등으로 역추적이 가능합니다. 그래서 비밀번호에는 반드시 솔트를 추가해야 합니다.

 

그래서 제가 요즘에 공부하고 있는 Dart언어에 이것을 어떻게 사용하냐를 정리해 보았스빈다.

Dart에서는 dart:io 라이브러리를 사용해 파일과 디렉토리를 다룹니다.

주요 클래스로는

File: 파일을 읽고 쓰기 위한 클래스입니다.

Directory: 디렉토리를 생성, 삭제, 탐색하는 클래스 입니다.

FileSystemEntitiy: 파일과 디렉토리의 부모 클래스 입니다.

RandomAccessFile: 파일에 임의 접근하는 클래스 입니다.

위 같이 4가지가 있습니다.

 

이제 사용예시를 정리해 보았습니다.

텍스트 파일 읽기(비동기)

import 'dart:io';

void main() async {
  final file = File('text.txt');
  try {
    String contents = await file.readAsString();
    print('파일 내용:');
    print(contents);
  } catch (e) {
    print('파일을 읽을 수 없습니다: $e');
  }
}

줄 단위로 읽기

import 'dart:io';

void main() async {
  final file = File('text.txt');
  try {
    Stream<String> lines = file.openRead()
      .transform(utf8.decoder)
      .transform(LineSplitter());
    await for (var line in lines) {
      print('줄: $line');
    }
  } catch (e) {
    print('에러: $e');
  }
}

 

파일 쓰기

텍스트 파일 쓰기(비동기)

import 'dart:io';

void main() async {
  final file = File('text.txt');
  await file.writeAsString('Hello, World!\n', mode: FileMode.write); // 덮어쓰기
  await file.writeAsString('새로운 줄 추가!\n', mode: FileMode.append); // 이어쓰기
}

 

파일 존재 여부 및 삭제

import 'dart:io';

void main() async {
  final file = File('text.txt');
  if (await file.exists()) {
    print('파일이 존재합니다.');
    await file.delete();
    print('파일을 삭제했습니다.');
  } else {
    print('파일이 없습니다.');
  }
}

 

디렉토리 생성 및 탐색

import 'dart:io';

void main() async {
  final directory = Directory('my_folder');
  if (!(await directory.exists())) {
    await directory.create();
    print('디렉토리 생성 완료');
  }
  // 디렉토리 내 파일 목록 출력
  await for (var entity in directory.list()) {
    print(entity.path);
  }
}

 

여기서 코드를 보면 async와 await라는 명령어가 보입니다. 하나씩 정리해 보면

async: 함수선언 앞에 붙여서 비동기 함수를 표시합니다. 그리고 async함수는 항상 Future 객체를 반환합니다.

await: Future를 반환하는 비동기 작업이 끝날 때까지 기다립니다. 그래서 await를 사용할 때는 함수가 반듯이 async로 선언되어 있어야 합니다. 흐름을 보면 await를 만나게 되면 작업이 끝날 때까지 다음 코드를 실행하지 않고 기다랍니다. 하지만 기다릴 동안 프로그램 전체가 멈추는게 아닙니다.

 

TDD(테스트 주도 개발)도 매우 중요합니다.

TDD(Test-Driven Development): 소프트웨어 개발 방법론 중 하나입니다. 실제 코드를 작성하기 전에 실패하는 테스트 코드를 먼저 작성하는 것이 특징입니다. 즉, 테스트가 통과할 때까지 실제 코드를 점진적으로 개발하는 것입니다.

개발자는 요구사항이나 기능 명세를 테스트 코드로 표현하고

그 테스트를 통과시키는 최소한의 코드를 작성한 뒤

코드를 리팩토링(구조개선)하는 과정입니다.

왜 TDD를 사용하냐

1. 버그 에방 및 빠른 발견

2. 요구사항 명확화

3. 리팩토링의 안정망

4. 자신감 있는 배포

 

TDD에는 대표적인 사이클이 있습니다.

Red-Green-Refactor

1. Red: 아직 구현하지 않은 기능의 테스트 코드를 작성합니다.

2. Green: 테스트가 통과하도록 가장 간단한 코드를 작성합니다.

3. Refactor: 테스트가 통과하는 것을 확인한 후 코드 구조를 더 깔끔하게 개선합니다.

 

이제 Dart언어에 TDD를 적용하는 법을 정리해보겠습니다.

먼저 pubspec.yaml에 dev_dependencies에 test 패키지를 추가해 줘야합니다.

테스트 파일은 보통 프로젝트의 test/ 폴더에 위치합니다.

그래서 파일명은 주로 *_test.dart형식으로 작성합니다.

예로 lib/calculator.dart 파일이 있다면 test/calculator_test.dart에  테스트 코드를 작성할 수 있습니다.

class Calculator { // 예시 클래스 생성
  int add(int a, int b) => a + b;
  int subtract(int a, int b) => a - b;
}
import 'package:test/test.dart';
import '../lib/calculator.dart';

void main() {
  group('Calculator 클래스 테스트', () {
    test('add 메서드는 두 수의 합을 반환해야 한다', () {
      final calc = Calculator();
      expect(calc.add(2, 3), equals(5));
    });

    test('subtract 메서드는 두 수의 차를 반환해야 한다', () {
      final calc = Calculator();
      expect(calc.subtract(5, 2), equals(3));
    });
  });
}

이제 코드를 보면 group()으로 여러 테스트를 묶어서 관련성을 표현할 수가 있습니다. 그래서 test()로 단일 테스트 케이스를 만들고

expect()로 실제 결과와 기대 결과를 비교합니다.
이제 터미널에 dart test를 입력하면 /test의 모든 테스트가 실행이 됩니다. 성공하면 초록, 실패하면 빨강이 나옵니다.

추가로 비동기함수에도 적용 할수 있습니다. 비동기 함수는 test 함수에 async를 붙이고 내부에서 await를 사용합니다.

test('비동기 함수', () async {
  Future<int> fetchNumber() async {
    await Future.delayed(Duration(milliseconds: 100));
    return 10;
  }

  expect(await fetchNumber(), equals(10));
});
test('에러 발생', () {
  int f() => throw Exception('에러!');
  expect(f, throwsException);
});

위 같은 코드로 에러발생 예외처리도 가능합니다.

 

감사합니다!