본문으로 건너뛰기

Chap.2 gussing-game

· 약 14분
brown
Frontend Developer

해당 학습 자료를 정리 및 수행한 기록입니다.

이번 장은 몇몇 일반적인 Rust 개념과 활용 방법을 배울 수 있습니다.

  • let
  • match
  • 메소드,
  • 연관함수(assiciated functions),
  • 외부 크레이트(external crates)

이번 장에서는 여러분이 직접 기초적인 내용을 실습합니다.

우리는 고전적인 입문자용 프로그래밍 문제인 추리 게임을 구현해 보려 합니다.

  1. 먼저 프로그램은 1~100 사이의 임의의 정수를 생성합니다.
  2. 다음으로 플레이어가 프로그램에 추리한 정수를 입력합니다.
  3. 프로그램은 입력받은 추리값이 정답보다 높거나 낮은지를 알려줍니다.
  4. 추리값이 정답이라면 축하 메세지를 보여주고 종료됩니다.

값을 변수에 저장하기

// 사용자 입력을 받고 결과값을 표시하기 위해서는 io (input/output) 라이브러리를 스코프로 가져와야 합니다.
// io 라이브러리는 std 라고 불리는 표준 라이브러리에 있습니다.

use std::io;

fn main() {
// print
println!("Guess the number!");
println!("Please input your guess.");

// mut string 변수 선언
let mut guess = String::new();

// input 받는 코드
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");

// 출력
println!("You guessed: {}", guess);
}

러스트에서 변수는 기본적으로 불변입니다.

let foo = 5; // 불변
let mut bar = 5; // 가변
  • String::new의 결과값인 새로운 String 인스턴스가 묶이는 대상이 됩니다.

  • String은 표준 라이브러리에서 제공하는 확장 가능한(growable) UTF-8 인코딩의 문자열 타입입니다.

  • ::new에 있는 ::는 new가 String 타입의 연관 함수 (associated function) 임을 나타냅니다.

  • 연관함수는 하나의 타입을 위한 함수이며, 이 경우에는 하나의 String 인스턴스가 아니라 String 타입을 위한 함수입니다.

  • 몇몇 언어에서는 이것을 정적 메소드 (static method) 라고 부릅니다.

  • new 함수는 새로운 빈 String을 생성합니다. new 함수는 새로운 값을 생성하기 위한 일반적인 이름이므로 많은 타입에서 찾아볼 수 있습니다.

요약하자면 let mut guess = String::new(); 라인은 새로운 빈 String 인스턴스와 연결된 가변변수를 생성합니다.

우리는 io의 연관함수인 stdin을 호출합니다:

io::stdin() .read_line(&mut guess)

stdin 함수는 터미널의 표준 입력의 핸들(handle)을 나타내는 타입인 std::io::Stdin의 인스턴스를 돌려줍니다.

코드의 다음 부분인 .read_line(&mut guess)는 사용자로부터 입력을 받기 위해 표준 입력 핸들에서 read_line 메소드를 호출합니다. 또한 read_line에 &mut guess를 인자로 하나 넘깁니다.

  • &는 코드의 여러 부분에서 데이터를 여러 번 메모리로 복사하지 않고 접근하기 위한 방법을 제공하는 참조자 임을 나타냅니다.
  • 참조자는 복잡한 특성으로서 러스트의 큰 이점 중 하나가 참조자를 사용함으로써 얻는 안전성과 용이성입니다.
  • 지금 당장은 참조자가 변수처럼 기본적으로 불변임을 알기만 하면 됩니다. 따라서 가변으로 바꾸기 위해 &guess가 아니라 &mut guess로 작성해야 합니다.

Result 타입으로 잠재된 실패 다루기

  1. read_line은 우리가 인자로 넘긴 문자열에 사용자가 입력을 저장할 뿐 아니라 하나의 값을 돌려 줍니다.
  2. 여기서 돌려준 값은 io::Result 입니다.
  3. 러스트는 표준 라이브러리에 여러 종류의 Result 타입을 가지고 있습니다.
  4. 제네릭 Result이나 io:Result가 그 예시입니다

Result의 variants는 Ok와 Err입니다.

Ok는 처리가 성공했음을 나타내며 내부적으로 성공적으로 생성된 결과를 가지고 있습니다.

Err는 처리가 실패했음을 나타내고 그 이유에 대한 정보를 가지고 있습니다.

io::Result가 Ok 값이라면 expect는 Ok가 가지고 있는 결과값을 돌려주어 사용할 수 있도록 합니다. 이 경우 결과값은 사용자가 표준 입력으로 입력했던 바이트의 개수입니다.

만약 expect를 호출하지 않는다면 컴파일은 되지만 경고가 나타납니다.

println! 변경자(placeholder)를 이용한 값 출력

println!("You guessed: {}", guess);

비밀번호를 생성하기

러스트는 아직 표준 라이브러리에 임의의 값을 생성하는 기능이 없습니다.

하지만 러스트 팀에서는 rand 크레이트를 제공합니다.

크레이트(Crate)를 사용하여 더 많은 기능 가져오기

  • 크레이트는 러스트 코드의 묶음(package)임을 기억하세요.
  • 우리가 만들고 있는 프로젝트는 실행이 가능한 binary crate 입니다.
  • rand crate는 다른 프로그램에서 사용되기 위한 용도인 library crate 입니다.

Cargo에서 외부 크레이트의 활용 예시

  • rand를 사용하는 코드를 작성하기 전에 Cargo.toml 을 수정
    • rand 크레이트를 의존 리스트에 추가
[dependencies]
rand = "0.8.5"

우리는 외부 의존성을 가지게 되었고, Cargo는 Crates.io 데이터의 복사본인 레지스트리(registry) 에서 모든 것들을 가져옵니다.

Crates.io는 러스트 생태계의 개발자들이 다른 사람들도 이용할 수 있도록 러스트 오픈소스를 공개하는 곳입니다.

레지스트리를 업데이트하면 Cargo는 [dependencies] 절을 확인하고 아직 여러분이 가지고 있지 않은 것들을 다운 받습니다.

이 경우 우리는 rand만 의존한다고 명시했지만 rand는 libc에 의존하기 때문에 libc도 다운 받습니다.

러스트는 이것들을 다운받은 후 컴파일하여 의존성이 해결된 프로젝트를 컴파일합니다.

크레이트를 새로운 버전으로 업그레이드하기

Cargo는 update 명령어를 제공합니다.

이것은 Cargo.lock 파일을 무시하고 Cargo.toml 에 여러분이 명시한 요구사항에 맞는 최신 버전을 확인합니다.

확인이 되었다면Cargo는 해당 버전을 Cargo.lock 에 기록합니다.

임의의 숫자를 생성하기

이제 rand 크레이트를 Cargo.toml 에 추가 했으니, rand를 사용 해 봅시다.

정말 놀라운 부분은 버전을 올려서 사용법이 달라졌는데, 사용법과 예시코드까지 보여줌 ㄷㄷ

use rand::Rng;
...
// let secret_number = rand::thread_rng().gen_range(1, 101);
let secret_number = rand::thread_rng().gen_range(1..101);

let mut i = 0;
loop {
if i > 100 {
break;
}
let secret_number = rand::thread_rng().gen_range(1..101);
println!("{} secret number is: {}", i, secret_number);
i += 1;
}
...

  • 먼저 use 라인인 use rand::Rng를 추가합니다.

    • Rng는 난수 생성기를 구현한 메소드들을 정의한 트레잇 (trait) 이며 해당 메소드들을 이용하기 위해서는 반드시 스코프 내에 있어야 합니다. 10장에서 트레잇에 대해 더 자세히 다룰 것입니다.
  • rand::thread_rng 함수는 OS가 시드(seed)를 정하고 현재 스레드에서만 사용되는 특별한 난수 생성기를 돌려줍니다.

  • 다음으로 우리는 gen_range 메소드를 호출합니다.

    • 이 메소드는 Rng 트레잇에 정의되어 있으므로 use rand::Rng 문을 통해 스코프로 가져올 수 있습니다.
    • gen_range 메소드는 두 개의 숫자를 인자로 받고 두 숫자 사이에 있는 임의의 숫자를 생성합니다. 하한선은 포함되지만 상한선은 제외되므로 1부터 100 사이의 숫자를 생성하려면 1과 101을 넘겨야 합니다.

Note: 크레이트에서 트레잇과 메소드, 함수중 어떤 것을 호출해야 할지 모를 수도 있습니다. 각 크레이트의 사용법은 크레이트의 문서에 있습니다.

Cargo의 다른 멋진 기능은 cargo doc --open 명령어를 사용하여 의존하는 크레이트의 문서를 로컬에서 모두 빌드한 다음, 브라우저에서 열 수 있다는 것입니다.

rand 크레이트의 다른 기능이 궁금하시면, cargo doc --open을 실행하고, 왼쪽 사이드바에서 rand를 클릭하여 알 수 있습니다.

비밀번호와 추리값을 비교하기

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
// --snip--

println!("You guessed: {}", guess);

match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}

Listing 2-4: 두 숫자를 비교한 결과 처리하기

  • Ordering은 Result와 같은 열거형이지만 Ordering의 값은 LessGreaterEqual입니다. 이것들은 여러분이 두 개의 값을 비교할 때 나올 수 있는 결과들입니다.
  • cmp 메소드는 두 값을 비교하며 비교 가능한 모든 것들에 대해 호출할 수 있습니다.
    • 이 메소드는 비교하고 싶은 것들의 참조자를 받습니다.
    • 여기서는 guess와 secret_number를 비교하고 있습니다.
    • cmp는 Ordering 열거형을 돌려줍니다.
  • 우리는 match 표현문을 이용하여 cmp가 guess와 secret_number를 비교한 결과인 Ordering의 값에 따라 무엇을 할 것인지 결정할 수 있습니다.

match 표현식은 arm 으로 이루어져 있습니다.

하나의 arm은 하나의 패턴 과 match 표현식에서 주어진 값이 패턴과 맞는다면 실행할 코드로 이루어져 있습니다.

러스트는 match에게 주어진 값을 arm의 패턴에 맞는지 순서대로 확인합니다.

match 생성자와 패턴들은 여러분의 코드가 마주칠 다양한 상황을 표현할 수 있도록 하고 모든 경우의 수를 처리했음을 확신할 수 있도록 도와주는 강력한 특성들입니다.

예제에서 사용된 match 표현식에 무엇이 일어날지 한번 따라가 봅시다.

  1. 사용자가 50을 예측했다고 하고 비밀번호가 38이라 합시다.
  2. 50과 38을 비교하면 cmp 메소드의 결과는 Ordering::Greater 입니다.
  3. match 표현식은 Ordering::Greater를 값으로 받아서 각 arm의 패턴을 확인합니다.
  4. 처음으로 마주하는 arm의 패턴인 Ordering::Less는 Ordering::Greater와 매칭되지 않으므로 첫번째 arm은 무시하고 다음으로 넘어갑니다.
  5. 다음 arm의 패턴인 Ordering::Greater는 확실히 Ordering::Greater와 매칭합니다!
  6. arm과 연관된 코드가 실행될 것이고 Too big!가 출력될 것입니다. 이 경우 마지막 arm은 확인할 필요가 없으므로 match 표현식은 끝납니다.

비교하기 위해서 string을 i32로 변환 해줘야 함

let guess: u32 = guess.trim().parse().expect("Please type a number!");

우리는 guess 변수를 생성했습니다.

잠깐, 이미 프로그램에서 guess라는 이름의 변수가 생성되지 않았나요? 그렇긴 하지만 러스트는 이전에 있던 guess의 값을 가리는(shadow) 것을 허락합니다

. 이 특징은 종종 하나의 값을 현재 타입에서 다른 타입으로 변환하고 싶을 경우에 사용합니다.

Shadowing은 우리들이 guess_str과 guess처럼 고유의 변수명을 만들도록 강요하는 대신 guess를 재사용 가능하도록 합니다. (3장에서 더 자세한 이야기를 다룹니다)

  1. 우리는 guess를 guess.trim().parse() 표현식과 묶습니다.
  2. 표현식 내의 guess는 입력값을 가지고 있던 String을 참조합니다.
  3. String 인스턴스의 trim 메소드는 처음과 끝 부분의 빈칸을 제거합니다.
    • u32는 정수형 글자만을 가져야 하지만 사용자들은 read_line을 끝내기 위해 enter키를 반드시 눌러야 합니다.
    • enter키가 눌리는 순간 개행문자가 문자열에 추가됩니다. 만약 사용자가 5를 누르고 enter키를 누르면 guess는 5\n처럼 됩니다. \n은 enter키, 즉 개행문자를 의미합니다. trim 메소드는 \n을 제거하고 5만 남도록 처리합니다.
  4. 문자열의 parse 메소드는 문자열을 숫자형으로 파싱합니다.
    • 이 메소드는 다양한 종류의 정수형을 변환하므로 우리는 let guess: u32처럼 정확한 타입을 명시해야 합니다.
    • guess 뒤의 콜론(:)은 변수의 타입을 명시했음을 의미합니다.
    • u32은 부호가 없는 32비트의 정수입니다.
    • parse 메소드의 호출은 에러가 발생하기 쉽습니다.
    • 만약 A👍%과 같은 문자열이 포함되어 있다면 정수로 바꿀 방법이 없습니다.
    • Result 타입으로 잠재된 실패 다루기” 에서 read_line와 비슷하게 parse 메소드는 실패할 경우를 위해 Result 타입을 결과로 돌려 줍니다. 우리는 이 Result를 expect 메소드를 사용하여 같은 방식으로 처리합니다.
    • 만약 parse 메소드가 문자열에서 정수로 파싱을 실패하여 Err Result variant를 돌려준다면 expect 호출은 게임을 멈추고 우리가 명시한 메세지를 출력합니다. 만약 parse 메소드가 성공적으로 문자열을 정수로 바꾸었다면 Result의 Ok variant를 돌려 받으므로 expect에서 Ok에서 얻고 싶었던 값을 결과로 받게 됩니다.

반복문을 이용하여 여러 번의 추리 허용

loop 키워드는 무한루프를 제공합니다.

정답 이후에 종료하기

	loop {
println!("Please input your guess.");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}

잘못된 입력값 처리하기

사용자가 숫자가 아닌 값을 입력했을 때 프로그램이 종료되는 동작을 더 다듬어 숫자가 아닌 입력은 무시하여 사용자가 계속 입력할 수 있도록 해 봅시다.

guess가 String에서 u32로 변환되는 라인을 수정하면 됩니다.

let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, };

  • expect 메소드 호출을 match 표현식으로 바꾸는 것은 에러 발생 시 종료되지 않게 처리하는 일반적인 방법입니다.

  • parse 메소드가 Result 타입을 돌려주는 것과 Result는 Ok나 Err variants를 가진 열거형임을 떠올리세요.

  • cmp 메소드의 Ordering 결과를 처리했을 때처럼 여기서 match 표현식을 사용하고 있습니다.

  • 만약 parse가 성공적으로 문자열에서 정수로 변환했다면 결과값을 가진 Ok 를 돌려줍니다.

  • Ok는 첫번째 arm의 패턴과 매칭하게 되고 match 표현식은 parse 가 생성한 num값을 돌려줍니다. 그 값은 우리가 생성하고 있던 새로운 guess과 묶이게 됩니다.

요약

이 프로젝트는 letmatch, 메소드, 연관함수, 외부 크레이트 사용과 같은 많은 새로운 러스트 개념들을 소개하기 위한 실습이었습니다.

Rust_PS in codewars

8 kyu

Convert boolean values to strings 'Yes' or 'No'.

fn bool_to_word(value: bool) -> &'static str {
match value {
true => "Yes",
false => "No",
}
}

DNA to RNA Conversion

fn dna_to_rna(dna: &str) -> String {
let n = dna.len();
let mut ans = String::new();
let mut idx = 0;

loop {
if (idx == n) {
break;
}
let t = dna.chars().nth(idx).unwrap();

match t {
'G' => ans.push_str("G"),
'C' => ans.push_str("C"),
'A' => ans.push_str("A"),
_ => ans.push_str("U"),
}

idx += 1
}
return ans;
}
//
fn dna_to_rna(dna: &str) -> String {
dna.replace("T", "U")
}
fn dna_to_rna(dna: &str) -> String {
dna.chars().map(char_conversion).collect()
}
fn char_conversion(c: char) -> char {
if c == 'T' {
return 'U';
}

c
}
fn dna_to_rna(dna: &str) -> String {
let mut res = String::new();
for s in dna.chars() {
match s {
'T' => res.push('U'),
_ => res.push(s),
}
}
res
}

Counting sheep...

fn count_sheep(sheep: &[bool]) -> u8 {
let mut cnt = 0;
for x in sheep {
if *x {
cnt += 1;
} else {
cnt += 0
}
}
cnt
}
//
fn count_sheep(sheep: &[bool]) -> u8 {
sheep // take the sheep array
.iter() // turn it into an iterable
.filter(|&&x| x) // filter it by taking the values in the array and returning only the true ones
.count() as u8 // count all of the elements in the filtered array and return a u8
}

Fake Binary

fn fake_bin(s: &str) -> String {
let mut ans = String::new();

for x in s.trim().split("").into_iter() {
if x == "" {
continue;
}
let num = x
.parse::<i32>()
.expect("please give me correct string number!");

// println!("{num}");
if num >= 5 {
ans.push_str("1");
} else {
ans.push_str("0");
}
}
println!("{ans}");
ans
}

//
fn fake_bin(s: &str) -> String {
s.chars().map(|c| if c < '5' {'0'} else {'1'}).collect()
}
fn fake_bin(s: &str) -> String {
s.chars()
.map(|c| match c {
'0'..='4' => '0',
'5'..='9' => '1',
_ => c
})
.collect()
}

Switch it Up!

fn switch_it_up(n: usize) -> &'static str {
match n {
0 => "Zero",
1 => "One",
2 => "Two",
3 => "Three",
4 => "Four",
5 => "Five",
6 => "Six",
7 => "Seven",
8 => "Eight",
9 => "Nine",
_ => "",
}
}
//
fn switch_it_up(n: usize) -> &'static str {
match n {
1 => "One",
2 => "Two",
3 => "Three",
4 => "Four",
5 => "Five",
6 => "Six",
7 => "Seven",
8 => "Eight",
9 => "Nine",
_ => "Zero"
}
}
fn switch_it_up(n: usize) -> &'static str {
match n {
0 => "Zero",
1 => "One",
2 => "Two",
3 => "Three",
4 => "Four",
5 => "Five",
6 => "Six",
7 => "Seven",
8 => "Eight",
9 => "Nine",
_ => panic!()
}
}

The Feast of Many Beasts

fn feast(beast: &str, dish: &str) -> bool {
return beast.chars().nth(0) == dish.chars().nth(0)
&& beast.chars().nth(beast.len() - 1) == dish.chars().nth(dish.len() - 1);
}
//
fn feast(beast: &str, dish: &str) -> bool {
beast.chars().next() == dish.chars().next()
&& beast.chars().last() == dish.chars().last()
}
fn feast(beast: &str, dish: &str) -> bool {
dish[..1] == beast[..1] && dish[dish.len()-1..] == beast[beast.len()-1..]
}

Function 2 - squaring an argument

fn square(n: i32) -> i32 {
n * n
}
//
fn square(n: i32) -> i32 {
n.pow(2)
}

Convert number to reversed array of digits

// error
// Creates a temporary which is freed while still in use Again slight_smile
let process = Command::new(location_test);
process.arg(address);

fn digitize(n: u64) -> Vec<u8> {
const RADIX: u32 = 10;
// your code here
let str = n.to_string();
let arr = str
.chars()
.rev()
.map(|x| x.to_digit(RADIX).unwrap())
.collect::<Vec<u32>>();

let mut ans: Vec<u8> = [].to_vec();
arr.into_iter()
.for_each(|val| ans.push(val.try_into().unwrap()));
println!("{ans:#?}");
return ans;
}
// u32 -> u8로 변경하는 부분
fn digitize(n: u64) -> Vec<u8> {
n
.to_string()
.chars()
.map(|c| c.to_digit(10).unwrap() as u8)
.rev()
.collect::<Vec<u8>>()
}

Chap.1 Hello-rust

· 약 2분
brown
Frontend Developer

학습 목표

2022-09월까지 학습자료에 대한 학습 마무리하기!!!

학습 자료

개발환경 세팅

  • rustup : 러스트 버전 및 러스트 관련 툴을 관리하는 커맨드라인 도구

  • brew install rustup-init

  • rustup-init

  • rustup update update

  • rustup self uninstall delete

  • rustup -  rust 버전 및 관련 도구들을 위한 커맨드라인 도구이다

    • rustc -> 컴파일러: rust 코드를 컴퓨터가 이해할 수 있는 언어로 변경해주는 도구
    • rustfmt -> 코드 포맷팅 도구
    • cargo -> rust의 의존성관리 도구이다.

Hello, Rust!

  • main.rsrustc로 바이너리파일로 컴파일 -> main 실행파일 생성
  • rustc main.rs -> ./main

Cargo 로 프로젝트 생성하기

  • cargo init
  • cargo project name
[package]
name = "hello_cargo"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
edition = "2018"

[dependencies]
...

Listing 1-2: cargo new 로 생성한 Cargo.toml 파일의 내용

이 포맷은 TOML (Tom’s Obvious, Minimal Language) 포맷으로, Cargo 설정에서 사용하는 포맷입니다.

Cargo 는 최상위 프로젝트 디렉토리를 README, 라이센스, 설정 파일 등 코드 자체와는 관련 없는 파일들을 저장하는 데 사용하기 때문에, 소스 파일은 src 내에 저장합니다.

Cargo 빌드 및 실행

  • cargo build 명령으로 프로젝트를 빌드할 수 있습니다.(target/debug/$projectName)
  • cargo run 명령어는 한번에 프로젝트를 빌드하고 실행할 수 있습니다.
  • cargo check 명령으로 바이너리를 생성하지 않고 프로젝트의 에러를 체크할 수 있습니다.
  • cargo build --release 명령어를 사용해 릴리즈 빌드를 생성할 수 있습니다.
    • 일반 빌드와 차이점은 target/debug 가 아닌 target/release 에 실행 파일이 생성된다는 점
    • 컴파일 시 최적화를 진행해, 컴파일이 오래 걸리는 대신 러스트 코드가 더 빠르게 작동하는 점

8월 회고 및 9월 계획

· 약 2분
brown
Frontend Developer

Intro


하고 싶은 것을 할 것인가 or 필요한 것을 할 것인가는 항상 많은 사람들이 고민하는 주제 일 것같다.

나 역시도 그렇다. 개발자로 더 나은 개발자가 되기 위해 공부해야 할 것은 널려있고, 시간은 한정 되어 있으니 선택과 집중은 필연적인 것이다.

작년에 rust에 꽂혀서 야심차게 작심 7일 공부한 기억이 있다.

그때 하다가만 이유 중 가장 큰 이유는 나는 프론트엔드 개발자이고 프론트엔드를 더 잘하는게 중요한데 러스트를 학습할 여유가 있는가 였다.

그렇게 1년의 시간이 지나고 다시 나의 마음에 돌아온 러스트..!

계기는 rust가 블록체인 업계에서 핫한 언어이고, 러스트로 솔라나체인에 스마트 컨트랙트를 작성할 수 있는 개발자가 좋은 대우를 받는다는 이야기였다.

원래 흥미도 있던 차에, 금전적인 이득을 더 취할 수 있는 기회라니 달콤하다.

물론 프론트엔드를 더 갈고 닦는게, 더 이득일 수도 있겠지만 하고싶으면 결국 하는게 사람이니까.

다시 해봐야지!

8월 회고


잘한 점

  1. 블로그를 시작한 것
    • 솔직히 아무도 안볼 줄 알았고 그게 당연한 것이라고 생각했는데, 의외로 봐주는 사람이 있다는 점이 더 열심히 하게 만들고 사람을 뿌듯하게 함
  2. obsidian을 사용한 것
    • 개인적인 경험으로는 obsidian이 노션보다 나은 것 같다.
    • obsidian 싱크를 하려다보니 저절로 일일커밋이 된다. 😎

개선할 점

이번달은 열심히 살아서 딱히 없는 것 같다.

아침에 일찍 일어나서 복싱가기 정도...!

9월 플랜


아래의 태스크들을 진행할 예정이다.

  • Rust 기초 및 문법 학습(다음달 스마트 컨트랙트 작성 목표)
  • 블로그::디자인 작업
  • 블로그::main page
  • PS::백준::단계 별 문제풀기(JS)
  • 깃허브::프로필 꾸미기

Keep going!

DocSearch with docusaurus

· 약 3분
brown
Frontend Developer

Intro

블로그 만든지 1달 정도 되었나...?

깜짝 놀랄만한 일이 생겼다.

솔직히 본인이 유명인도 아니고! 홍보를 하는 것도 아니고!! 그렇다고 대단한 글을 작성하는 것도 아닌데!!!

생각보다 많은 분들이 이 블로그를 봐주신 것이다!!!!! 😎

searchConsole ga

모든 콘텐츠가 한국어 인데 why...?

Thanks!!!

도대체 어떻게 알고 봤는지 신기하기만 하다.

그래서 생각하고 있던 docusaurus 작업 중

  1. 검색 붙이기
  2. 테마 수정하기
  3. 메인 수정하기

검색 붙이기를 하면서 + 후기를 남긴다.

DocSearch

알게 된 것은 친구의 블로그 덕분이다.

dgle

아니 저 기능 뭐지 탐난다...! 라는 생각으로 작업했다.

1. 공식문서 체크

docusaurus알골리아 DocSearch에 대한 최고 수준의 지원을 제공합니다.

2. apply docsearch program

서비스는 모든 오픈 소스 프로젝트에 대해 무료입니다.
대상 여부를 확인하고 DocSearch 프로그램에 지원하세요.

docsearch program에 등록해본다.

본인 블로그는 아직 빌딩단계라서 해당없다는 회신을 받았는데, 딱히 상관은 없다.

3. 알고리아 회원가입

algolia에서 회원 가입을 진행해준다.

4. create application

어플리케이션을 생성해주는데

app

  1. app 이름 적고
  2. 꼭 FREE Plan을 선택하자!!
  3. data는 샘플데이터 같은 것 넣으면 된다.
  4. index name 은 사용하는 값이다.

5. API Key 체크

overview 화면에서 API Keys를 누르자

사용하는 key는 Application ID,Search-Only API Key,Admin API Key이다.

6. Data crawling

최신 권장방식 문서이다.

본인은 아래의 방법을 사용했다.

이 방식은 Legacy 입니다!

관련 문서

  1. 이 단계에서는 도커 이미지를 통한 데이터 크롤링을 위해 docker, jq가 설치 되어 있어야 한다.

    • brew install --cask docker
    • brew install jq
  2. .env 작성 환경 변수를 작성해야 한다.

    APPLICATION_ID=어플아이디
    API_KEY=어드민키
  3. project 최상단에 config.json 작성 참조 링크 index_name, start_urls,sitemap_urls만 본인에 맡게 변경하시라.

    {
    "index_name": "braurus", // 1
    "start_urls": ["https://braurus.dev/"], // 2
    "sitemap_urls": ["https://braurus.dev/sitemap.xml"], // 3
    "sitemap_alternate_links": true,
    "stop_urls": ["/tests"],
    "selectors": {
    "lvl0": {
    "selector": "(//ul[contains(@class,'menu__list')]//a[contains(@class, 'menu__link menu__link--sublist menu__link--active')]/text() | //nav[contains(@class, 'navbar')]//a[contains(@class, 'navbar__link--active')]/text())[last()]",
    "type": "xpath",
    "global": true,
    "default_value": "Documentation"
    },
    "lvl1": "header h1",
    "lvl2": "article h2",
    "lvl3": "article h3",
    "lvl4": "article h4",
    "lvl5": "article h5, article td:first-child",
    "lvl6": "article h6",
    "text": "article p, article li, article td:last-child"
    },
    "strip_chars": " .,;:#",
    "custom_settings": {
    "separatorsToIndex": "_",
    "attributesForFaceting": ["language", "version", "type", "docusaurus_tag"],
    "attributesToRetrieve": [
    "hierarchy",
    "content",
    "anchor",
    "url",
    "url_without_anchor",
    "type"
    ]
    }
    }
  4. 스크래퍼 이미지 실행
    docker run -it --env-file=.env -e "CONFIG=$(cat ./config.json | jq -r tostring)" algolia/docsearch-scraper

  5. 알고리아 본인 앱 화면에 보면 데이터가 갱신 되어 있을 것이다.

7. docusaurus.config.js

이 문서를 참조 하자!

// 본인 코드
...
algolia: {
// 알골리아에서 제공한 appId를 사용하세요.
appId: "BW2ZDYYT4N",
// 공개 API 키: 커밋해도 문제가 생기지 않습니다.
apiKey: "4a0c2546c188aacd5f5277a7a9b34896",
indexName: "braurus",
contextualSearch: true,
},
...

드디어 완성이다.

searchBtn


검색기능을 붙이고 이 글을 쓰기까지 생각보다 시간이 걸렸지만, 누가 볼 수도 있다고 생각하니 살짝 기대 된다.



Reference

생산성에 대해서

· 약 3분
brown
Frontend Developer

서론


시간은 모두에게 공평하다. 하루 24시간 그 이상을 사용하는 사람도, 그 이하를 사용하는 사람도 없다.

세상은 불공평하지만, 시간은 공평하고 개인이 할 수 있는 최선은 본인에게 주어진 시간을 최대한 효율적으로 쓰는 것이다.

하지만 시간을 잘 활용하는 것은 어렵다. 30년 동안 살면서 본인의 행동에서, 혹은 학창시절, 대학시절등 주변에서 느낀 것은, 이 사람이 열심히 사는구나 싶은 사람은 사실 100명중 10 ~ 15명 정도 인것 같다.

본인 역시 열심히 사는 사람 옆에 열심히 살지 않는 사람이었고, 중고딩 시절에 대학생까지도 게임을 엄청 했었다.

그렇게 25살까지 해놓은 것도 없이, 하고 싶은 것도 없이 놀았었다. 생각해보면 이 시간이 정말 아깝다.

그러다 26살에 드디어 미래에 대한 위기감 + 해외취업을 한번 해봐야겠다는 목표를 가지고 토익점수 900 이상 올리고, 학교에서 진행했던 어학연수도 가고, 무역관련 자격증(무역영어 1급, 국제무역사 1급, 유통관리사 2급)들도 따고 해서 k-move 프로그램으로 결국 취업도 했었다.

그렇게 직장생활을 시작했지만 뭔가 일반 사무직은 전문성이 약한 것(개인적인 의견) 같았고, 뭔가 비전이 보이지 않았다.

그러다 개발자가 원래 관심도 있었고 글로벌 시대에 좋을 것 같아서, 20년 4월부터 개발공부를 본격적으로 시작했고 21년 6월에 개발자로 커리어 전환을 할 수 있었다.

서론이 장황했는데 26살 부터는 나름 인생을 열심히 살려고 노력했었고, 갈수록 더 열심히 살고 있다. 그러다보니 이 주제에 대해 고민을 많이 했었다.


열심히 산다는 건?


열심히 사는 것의 첫 번째는 시간관리라고 생각한다.

시간관리에 대해 신경쓰지 않으면 하루에 날리는 시간이 생각보다 많고, 나이 먹을수록 시간은 더 빨리간다.

첫 번째 스텝은 특별한 것은 없이 그렇게 날리는 시간들을 체크하고 최대한 줄이는 것이다.

다만 그 순간의 귀찮음, 하기 싫음 등을 컨트롤 하는 것이 필요한데, 그러한 감정들은 일시적이라는 것을 명심하자.

두 번째 스텝은 일상을 단순화 하는 것이다.

본인이 느끼기에 에너지는 한정적이고 하루에 할 수 있는 일의 양도 한계치가 있는 것 같다.

그러니 불필요한 일들은 하지 않고, 내가 알 필요 없는 것들에 관심을 끄는게 시간관리에 좋다고 생각한다(단점은 재미없는 인간이 된다).

그러니 본인이 하루에 할 수 있는 양은 채우고 휴식을 하면 된다 생각한다.


생산성 관리


개인적으로 느끼기에, 본인은 하루에 많은 시간을 개발에 쏟고 있다고 느낀다.

직장인으로서 개발하는게 일이고, 그외의 시간에도 자체 야근 😵, 개발 학습, PS 등 열심히 하려고 노력하고 있다.

잘하는 개발자가 되고 싶으니까. 돈도 많이 벌면 더 좋고 😎

어짜피 인생은 100명중에서 노력하는 15명 안쪽에서 경쟁하는 것이다.

개발자 직군은 아무래도 공부를 많이하는 성향의 사람들이 할 확률이 높으니 개발자의 30%라고 가정해보자.

이 레벨에서는 업무역량향상을 위해 시간을 투자하는 것은 기본이고 투자한 시간(input)대비 얼마만큼의 생산성(output)을 뽑아내는가가 차이를 만들어낸다고 생각한다.

그래서 어떻게 해야 생산성을 올릴 수 있을까?

시간 대비 많은 결과물을 내고, 많은 분량의 학습을 소화할 수 있을까?

이것이 요즘 난제다.

그래서 느낌 오는 컨텐츠들을 정리 해봤다.

물론 시간관리 + 생산성관리까지 한다고 엄청 높은 위치까지 올라간다는 보장은 없다.

그럼에도 불구하고, 우리가 노력해야 하는 이유는 노력 안한 자신보다 더 나아질 것이라는 믿음 때문이다.

헬스쪽에는 자신의 유전자내에서, 최대를 뽑아내는 것이 가장 멋진 것이라는 말이 있다.

요즘 삘 받아서 열심히 살려고 계속 노력하고 있는데, 언제쯤 번아웃이 올지 기대된다.

사실 열심히 살 수 밖에 없는 것 같다. 뭐라도 하지 않으면 뒤쳐질 것 같은 불안감이 있으니까

학창시절에 지금처럼 살았으면 훨씬 잘됐을 것 같지만, 그땐 몰랐지


obsidian custom

옵시디언 처음 쓰시는 분들 첫 세팅할 때 쓰시면 좋을 것 같습니다.

obsidian-template

blog 초기 setting 후기2

· 약 2분
brown
Frontend Developer

1편에서 이어집니다

역시 이런건 작업하면서, 글도 써야한다.

이틀밖에 안됐는데 가물가물...

4. domain 구매

배포 및 호스팅은 vercel 에서 알아서 해준다. awesome!

도메인을 설정하려면 당연하게도 도메인을 사야하는데, 중요한 것은 어디서 살 것 인가이다.

처음에는 그냥 국내업체에서 구매할까 했지만, 아는 사람한데 물어보고 검색 좀 해보니까

해외 도메인 업체를 이용하는 것으로 그 중에서도 porkbun 으로 결정했다.

why? -> 제일 싸니까

도메인은 고민 끝에 brown + docusaurus = braurus

거기에 개발자 티내야 하니까 braurus.dev로 결정해서 구매했다.

구매하는 방법은

  1. 회원가입
  2. 도메인을 선택
  3. 결제

본인은 카드도 오직 체크카드, 그것도 국내결제 밖에 안되는 카드인데!?

라고 생각하면서 진행했는데, 구글 결제 선택하니까 알아서 플레이스토어로 어떻게 결제가 되더라.

그렇게 생각보다 싸게 도메인을 일단 2년치로 구매했다.

5. domain assign by vercel

개인 도메인을 구입했다면, vercel 프로젝트 세팅의, domain에서 등록하고 그냥 하라는대로 하면 된다.

  1. DNS RECORDS 세팅 ~~2. NAMESERVERS -> 포크번 디폴트 네임서버를 버셀 네임서버로 변경 ns1.vercel-dns.com
    ns2.vercel-dns.com

버셀 네임서버로 변경하고 버셀에 레코드 등록하니까 search-console에서 인증이 안됌

6. GTAG

개인 도메인을 붙여놓으니, 물론 아무도 안보겠지만 정말 아무도 안보는지 매우 궁금해졌다.

그런고로 GTAG를 달아야겠다!

  1. 링크로 들어가서 gtag를 만들고 측정 ID 저장

docusaurus 에서는 gtag 관련 플러그인을 제공해준다.

  1. npm install --save @docusaurus/plugin-google-gtag 로 설치를 하고,
  2. docusaurus.config.js에서 위의 측정 ID를 설정하면 끝

gtag 추적 아이디는 어짜피 사이트에서 다 보이니 굳이 감출 필요는 없는 것 같다.

측정 ID스트림 URL 일치가 중요!

잘 등록되면 저렇게 뜬다.

gtag

7. Search Console

하는 김에, search console도 해보자!

도메인을 산 사람들은 왼쪽이다. 본인의 도메인을 넣고 누르면 DNS 레코드를 통해 도메인 소유권을 확인하라고 하는데 어렵지 않다.

그냥 등록하면 끝인 작업인데, 도메인은 포크번에서 구입하고, 버셀네임서버로 변경한 뒤 버셀에다 등록하니까 도메인 소유권 인증이 안되더라. 포크번 네임서버로 변경하고 거기다 등록하니 마무리 되었다.

google search

참고로 구글 애널리틱스, 서치 콘솔 둘다 등록 하면 search console insights라는 페이지를 볼 수 있다.

8. sitemap

사이트 맵 주소는 $address/sitemap.xml이다 docusaurus.config.js 에 url을 본인 도메인으로 변경해주고, search console에 제출하자.

blog 초기 setting 후기 및 주 회고

· 약 2분
brown
Frontend Developer

1. blog를 시작해보자

블로그를 시작하자 라는 구체적인 영감을 받은 것은 퇴직한 동료개발자가 러브콜을 받는 모습을 봤을 때이다. 나도 노력한 증거를 남겨야지

왜 노력한 증거를 남기는 방식이 블로그인가?

만만해서, 이참에 글 쓰는 연습을 해보려고

2. docusaurus 세팅

수많은 static site 생성 tool(Jekyll, Gatsby... )중에서 왜 docusaurus 인가?

선임개발자였던 luke의 추천 facebook에서 만들어서 트렌디해보여서? 😎 너무 쉽게 나름 이쁜 블로그가 생겨버려서

docusaurus 는 그냥 문서보고 하면 된다. 심지어 번역도 되어있고 쉽다. 사실 글 작성외에 아직 기본 세팅에서 바꿔본게 없다...

3. deployment by vercel

이것도 정말 미쳤다. workflow도 작성할 필요없이 그냥 레포지토리만 등록하니까, defalut branch에 push만 하면 자동으로 배포된다.

client-docusaurus-brown2243.vercel.app 이런 식으로 서브 도메인으로 제공해준다.

여기까지의 상태로 3주 정도 쓰고 있었다. 어짜피 처음 블로그를 개설하고 글 몇개 쓴다고 갑자기 방문자들이 엄청 생기는 건 아니니까 천천히 개선해 나갈 생각이었다.

그런데 obsidian 을 이쁘게 세팅하고 나니, 블로그의 글이 너무 밋밋해 보이는 문제가 생겼다.

blog

img1

obsidian

img1 그래서 블로그 초기 세팅 작업을 하려고 한다. blog 초기 setting 후기2로 계속


8월 2주차 주 회고

뭔가 글을 잘 안쓸 것 같고, 주제도 별로 없을 것 같아 주 회고는 반드시 작성해야지 라는 취지로 주 회고를 기획 했는데 생각보다 쓸게 많고 재밌다.

이번주에 글을 4편이나 작성해서 , 주 회고를 따로 쓰기보단 묶어서 쓴다.

나름 열심히 보낸 일주일이었고, 앞으로도 Keep going 해봐야지!

Obsidian custom 후기

· 약 2분
brown
Frontend Developer

Obsidian theme custom 후기

최근 판교뚜벅초님 영상을 보고 옵시디언에 꽂혔다.

올려보자 나의 생산성... 제발!!!

그러면서 옵시디언 사용법 + 제텔카스텐에 대한 글을 하나 써보려 했는데...

obsidian 기본 테마도 나름 괜찮지만 새로운 테마를 적용하고, 커스텀한 후기를 먼저 작성하려고 한다.

이거는 지금 안쓰면 잊어먹는다...

obsidianate theme

open settings -> appearance -> theme 보면 사람들이 올려 놓은 테마를 볼 수 있다.

본인의 눈에는 그 중에서 obsidianate가 젤 이뻐서 다운 받았다.

warp 와 느낌이 유사한게 아주 마음에 들었다.

막상 사용해보니 아쉬운 점이 보이더라

  • [] 가 보이지가 않는다(link 작성할 때 불편)
  • 에디터 모드의 [] 안에 붉은 색 라인이 별로다.
  • strong 그라디언트가 내눈에 별로다.
  • 그외...

그러니까 이 글은 obsidianate 커스텀 글이다.

필자는 옵시디언을 안지도 1주일이 안됐고, css는 어제 오늘 합쳐 이틀 커스텀 했다. 고로 초급자용 글이다.

css 파일 등록

.obsidian/snippets경로에 css파일 생성 open settings -> appearance -> css snippet 옵션에서 플러그인 처럼 css로 켜주어야 한다.

켜줘야 하는 걸 간과해서, 안되는 줄 알고 리로딩 엄청 했었다... 삽질 😵

본인이 작성한 css파일이든, $theme.css 파일이든 수정하면 바로 적용 된다.

어떻게 바꿀 것인가

  1. 설치한 theme의 깃허브를 체크해라

    • 어떻게 변경 하라는 건지에 대한 내용이 있으면, Thanks
    • .obsidian/themes 폴더안에 $theme.css 가 아마 있을 것이다.
    • $theme.css 파일을 분석하다보면 느낌온다.
    • obsidianate github
  2. obsidian에 어떻게 custom css를 적용하는지 체크해라

위 사항은 공통이고 아래는 본인이 작업한 방식이다.

  1. colorhunt에서 원하는 색조합을 찾았다.
  2. 변수 세팅하고 노가다 시작...!

옵시디언은 오픈 소스가 아니다.

그러므로 내가 잘못 안것이 아니면 위의 1,2번 을 보고 뭐랄까 장님 코끼리만지듯 진행해야 한다.

img1

img2

  1. .workspace

  2. .side-dock-ribbon

  3. workspace-leaf

    • .workspace-leaf-content[data-type="search"] 이런 식으로 개별 leaf를 선택할 수 있지만 명칭을 알 수가 없다 ㅋㅋ;
    • .workspace-leaf.mod-active 로 클릭 상태 시 css 처리
  4. nav folder ~~~

알아내고 변경했던 섹션 클래스들은 위처럼 되어 있고, 개별 part들은 obsidian css 여기서 찾으면 쉽게 변경 할 수 있다.

이글을 작성하는 현재 상태 너무 빤딱빤딱한가..? img3

obsidian-template

작성한 커스텀 css 및 shortcut을 나름 vscode 처럼 맞춘 obsidian-template repo를 참고해주시라!!!

Nextjs Error Boundary 적용기

· 약 1분
brown
Frontend Developer

Nextjs Error Boundary 적용기

신생 프로젝트를 작업하면 이것저것 할게 많다. 그래서 많이 배운다.

ErrorBoundary 적용해야 한다고 생각 했지만, 더 시급한 업무 때문에 우선 순위에서 약간 밀려있었다.

그러다 테스트 환경에서 사파리로 특정 페이지 접속 시 에러가 뜬다는 소식을 들었다.

결론부터 말하자면 원인은 Array.prototype.at() 때문이었다.

최신 문법이라서 브라우저 호환성을 체크 했어야 했는데...! 😵

그래서 관련 부분 수정을 하고, 이참에 아래의 방식으로 ErrorBoundary를 작업 했다.

  1. 공식문서 체크
    내용중에서 이러한 부분이 있다.
    To use Error Boundaries for your Next.js application, you must create a class component ErrorBoundary
    클래스 컴포넌트 써본 지 1년은 넘은 것 같은데 + 예시코드가 jsx밖에 없네...
  2. TS 예시코드 체크
    서치를 해보니 해당 링크에 원하는 예시 코드가 있었다.

그런데 바로 설득 당했다.

Option 1: Using react-error-boundary React-error-boundary - is a lightweight package ready to use for this scenario with TS support built-in. This approach also lets you avoid class components that are not that popular anymore.

이렇게 react-error-boundary를 적용했다.

딱히 설명 할 것도 없이 보고 하면 된다.

8월 1주차 회고(Weekly Retrospective)

· 약 2분
brown
Frontend Developer

8월 1주차 회고(Weekly Retrospective)

사내 프로젝트의 2주 스프린트를 마무리했고, 무중단배포 기능과 상품 상세 페이지를 배포 했다.

개인적으로 아쉬운 것은 기존에 넣기로 했던 상품관련 차트를 넣지 못한 것이다.

특정 차트를 D3로 구현하라는 요구사항이었는데, 여기서 나의 실수는 D3 라이브러리에 대해 잘 모르면서 알겠다고 동의를 한 부분이다.

기존에 거래소일때 trading-view 라이브러리, chartjs등을 사용해봤고, 간단한 차트는 캔버스로 그려도 봤으니 사실 쉽게 생각했었다.

그런데 라이브러리를 설치해서 사용해보고, 알아볼수록 러닝커브가 생각보다 훨씬 높았다. 예전 취준 할 때 찍먹해본 threejs수준이 아닌가 😵 !

안그래도 메인프로젝트 + 어드민 + 기타 업무량이 많았는데, D3의 최소 1 ~ 2주를 요구하는 러닝커브에 굴복해서 결국 그 부분은 다르게 대체를 했다.

일은 일대로 열심히 했는데, 그렇게 마무리하니까 좀 찝찝한 마음이 남았다. 앞으로는 이러한 부분에서 더욱 확인을 하고 진행을 해야지.


일일커밋을 하겠다고는 생각 안했지만, 퇴근하고 뭔가 새로운 걸 학습 및 구현해보는 참 개발자가 되고싶은데 현실은 퇴근을 못하고 있다;;

기존에 둘이서 할 때도 그랬지만 지금은 혼자서 프론트단을 맡아서 하고 있다보니, 뭐랄까 내 개인 프로젝트가 아님에도 내 개인 프로젝트 같다.

회사 일은 업무시간 내에 처리하고 싶은데, 참 그게 쉽지 않은 것 같다. 그래서 개인 시간이 정말 팍팍하지만, 주 회고는 꾸준히 써야지!

docker-compose volume

docker에서 volumes으로 연결할 때는 꼭 경로를 폴더로 잡아줘야 한다.

경로를 파일로 잡아주면 파일 자체는 컨테이너에서 찾을 수 있지만, 컨테이너 외부에서 파일 내용을 변경해도 변경사항 전달이 안된다.

폴더로 잡아주면, 파일의 변경사항이 공유 된다.