프로그래머에게 한계가 필요한 이유

한계는 예술, 디자인 및 생활을 향상시킵니다.

우리는 No Limits 또는 Push the Limits 문화에서 왔지만 실제로는 한계가 필요합니다. 우리는 그들과 함께하는 것이 좋지만 올바른 한계가되어야합니다.

더 나은 음악을위한 검열

노래 나 책 또는 영화에서 말할 수있는 것에 대해 외부 적으로 부과 된 제한이 있었을 때, 작가는 특정한 의미를 나타 내기 위해 은유에 의존해야했습니다.

1928 Cole Porter의 클래식을합시다 (사랑에 빠지게합시다). 우리 모두는 그들이“It”의 의미를 알고 있으며“Fall in Love”가 아닙니다. 검열을 피하기 위해 제목에 괄호를 추가해야한다고 생각합니다.

2011 년으로 돌아가서 손잡이에있는 3 개의 6 개의 마피아 슬롭을보십시오. 실제로 은유 적 인 첫 번째 스탠자를 제외하고 나머지 가사는 코에 있습니다.

Cole Porter의 노래는 예술의 장점 (또는 그 부족)을 잠시 제쳐두고 상상력에 아무런 영향을주지 않으면 서 3 개의 6 개의 마피아 노래가 우리에게 호기심을 불러 일으키는 것을 암시합니다.

문제는 Three 6 Mafia의 가사에 설명 된대로 사랑 만들기에 가입하지 않으면 최악의 저속한 상태에서 노래를 찾아야한다는 것입니다. 그러나 Cole Porter와 함께 청취자는 자신의 개인적인 사랑 만들기 환상을 불러 일으킬 수 있습니다.

따라서 제한으로 인해 더 큰 호소력을 얻을 수 있습니다.

상어가 작동하지 않습니다

죠스 이야기를 들려주는 스티븐 스필버그의 원래 계획은 상어를 보여주는 것이었다. 그러나 항상 깨졌습니다. 대부분의 경우 영화의 스타 상어를 보여줄 수 없었습니다.

기계적인 어려움으로 Spielberg가 할 수있는 일에 제한을 두지 않으면 Blockbuster에 소개 된이 영화는 현재 화신에 존재하지 않을 것입니다.

이 영화는 왜 상어가 보이는 영화보다 훨씬 우월합니까? 다시 한 번 누락 된 부분이 각 뷰어에 의해 채워지기 때문입니다. 그들은 자신의 개인적인 공포증을 가지고 스크린에 투사합니다. 따라서 각 시청자에게 두려움이 개인화됩니다.

애니메이터들은 수년간 이것을 알고있었습니다. 충돌 화면을 재생 한 후 여파로 잘라내십시오. 여기에는 두 가지 이점이 있습니다. 충돌에 애니메이션을 적용 할 필요는 없으며 뷰어의 마음에 충돌이 발생합니다.

대부분의 사람들은 밤비의 어머니가 총에 맞았다 고 생각합니다. 우리는 그녀가 총에 맞지 않을뿐만 아니라 그녀가 총에 맞은 후에도 결코 보지 못합니다. 그러나 사람들은 둘 다 본 것을 맹세합니다. 하지만 절대로 표시되지 않습니다.

따라서 제한으로 인해 상황이 개선 될 수 있습니다. 훨씬 낫다.

선택, 어디서나 선택

당신이 화가라고 상상해보고 그림을 그려달라고 부탁합니다.“아름다운 그림을 그리십시오. 정말 좋아할만한 것입니다.”

이제 스튜디오에 들어가서 빈 캔버스를 쳐다보고 앉아 있습니다. 응시하고 페인트 칠을 할 수 없습니다. 왜?

가능성이 너무 많기 때문입니다. 말 그대로 무엇이든 페인트 할 수 있습니다. 나는 당신에게 제한을 두지 않았습니다. 그리고 이것은 당신을 마비시킨 문제입니다. 이것을 선택의 역설이라고합니다.

대신, 내가 원하는 풍경을 그리겠다고 말하면, 무한한 가능성의 절반을 제거 할 것입니다. 여전히 무한한 가능성이 남아 있지만 초상화에 대한 생각은 빨리 사라질 수 있습니다.

더 나아가서 황금빛 일몰 동안 해변에서 파도가 부서지는 바다 경치를 좋아한다고 말하면 가능한 많은 그림이있을 것입니다. 그러나 이러한 제한 사항은 실제로 페인트 할 것을 생각하는 데 도움이됩니다.

알기 전에 바다 경치를 스케치 할 수 있습니다.

따라서 제한 사항으로 인해보다 쉽게 ​​만들 수 있습니다.

하드웨어가 소프트웨어보다 쉽다

하드웨어를 사용하면 컴퓨터의 여러 구성 요소가 공유하는 트랜지스터 또는 커패시터를 볼 수 없습니다. 키보드 회로의 저항은 그래픽 카드에 의해 액세스, 공유 또는 영향을받을 수 없습니다.

그래픽 카드에는 독점적으로 소유하고있는 자체 저항이 있습니다. 하드웨어 엔지니어는 더 많은 저항을 판매하기 때문에이 작업을 수행하지 않습니다. 그들은 선택의 여지가 없기 때문에 그렇게합니다.

우주의 율법은 혼란을 일으키지 않고서는 이것이 불가능하다고 지시합니다. 우주는 하드웨어 엔지니어에게 규칙을 부과합니다. 즉, 가능한 것을 제한합니다.

이러한 제한으로 인해 소프트웨어보다 하드웨어에 대한 생각과 작업이 쉬워집니다.

소프트웨어에서 불가능한 것은 없습니다

이제 거의 모든 것이 가능한 소프트웨어로 전환하십시오. 소프트웨어 엔지니어가 프로그램의 모든 부분과 변수를 공유하는 것을 제한하는 것은 없습니다. 이것을 전역 변수라고합니다.

어셈블리 언어 프로그래밍에서는 코드의 특정 지점으로 건너 뛰어 실행할 수 있습니다. 언제든지이 작업을 수행 할 수 있습니다. 프로그램이 의도하지 않은 코드를 실행하게하는 데이터에 쓸 수도 있습니다. 이 방법은 버퍼 오버플로 취약점을 악용하는 해커가 사용하는 방법입니다.

일반적으로 운영 체제는 프로그램이 수행 할 수있는 작업을 프로그램 외부의 작업으로 제한합니다. 그러나 프로그램이 소유 한 코드 및 데이터에 대해 프로그램이 수행 할 수있는 작업에는 제한이 없습니다.

소프트웨어 작성 및 유지 관리를 어렵게 만드는 한계가 없습니다.

소프트웨어 개발을 올바르게 제한하는 방법

우리는 소프트웨어 개발에 한계가 필요하다는 것을 알고 있으며 경험을 통해 다른 창조적 노력의 한계가 우리에게 도움이 될 수 있음을 알고 있습니다.

또한 우리는 우리의 패러다임을 제한하기 위해 사회에 의존하여 코드를 무작위로 검열하거나 일부 기계적 결함에 의존 할 수 없다는 것을 알고 있습니다. 또한 사용자는 설계시 적절한 제한이 필요한 정도로 요구 사항을 지정할 수 없습니다.

우리는 스스로를 제한해야합니다. 그러나 우리는 이러한 제한이 유익한 지 확인하고 싶습니다. 그렇다면 어떤 한계를 정하고 어떻게 결정해야합니까?

그 질문에 답하기 위해, 우리는 적절한 한계를 추구하는 데 도움이되도록 우리의 경험과 수년간의 경험에 의존해야합니다. 그러나 우리가 가진 가장 유용한 도구는 과거의 실패입니다.

과거 행동의 고통, 예 : 스토브를 만지면 비슷한 고통을 피하고 싶다면 우리 자신에게 어떤 제한을 두어야하는지 알려줍니다.

내 사람들을 보내줘

초기에는 사람들이 코드를 뛰어 넘어 프로그램을 작성할 수있었습니다. 이런 종류의 코드를 따르는 것이 스파게티 한 그릇에 단일 가닥을 따르는 것과 같으므로 이것을 스파게티 코드라고합니다.

업계는이 관행이 비생산적이라는 것을 깨달았고 처음에는 그것을 허용하는 언어로 작성된 코드에서 GOTO 문 사용을 금지했습니다.

결국 새로운 프로그래밍 언어는 GOTO를 지원하지 않는 장점으로 팔렸습니다. 이것을 구조 프로그래밍 언어라고합니다. 오늘날 모든 주요 고급 언어는 GOTO가 없습니다.

이런 일이 발생했을 때, 일부 사람들은 새로운 언어가 너무 제한적이며 방금 GOTO를 가지고 있다면 더 쉽게 코드를 작성할 수 있다고 불평했습니다.

그러나 더 진보적 인 마음이 이겼고 우리는 그러한 파괴적인 도구의 멸종에 대해 감사해야합니다.

진보적 인 마음이 깨달은 것은 코드가 쓰여지거나 변경되는 것보다 더 많이 읽혀진다는 것입니다. 따라서 작은 그룹의 소 구경에게는 조금 덜 편리 할 수 ​​있지만 장기적으로는이 제한으로 훨씬 나아질 것입니다.

컴퓨터는 여전히 GOTO를 수행 할 수 있습니다. 실제로, 그들은 필요합니다. 우리는 업계에서 프로그래머가 직접 사용하는 것을 제한하기로 결정했습니다. 모든 컴퓨터 언어는 GOTO를 사용하는 코드로 컴파일됩니다. 그러나 언어 디자이너는 더 훈련 된 분기를 사용하는 구문을 만들었습니다. for 문을 종료하기 위해 break 문 사용

소프트웨어 산업은 언어 설계자들이 규정 한 한계로부터 큰 혜택을 받았습니다.

족쇄를 가져와

그렇다면 오늘날의 GOTO는 무엇이며 언어 디자이너가 의심하지 않는 프로그래머를 위해 무엇을 보유하고 있습니까?

이에 대한 답을 얻으려면 매일 발생하는 현재 문제를 살펴 봐야합니다.

  1. 복잡성
  2. 재사용 성
  3. 글로벌 가변 상태
  4. 동적 타이핑
  5. 테스팅
  6. 무어의 법칙

위의 문제를 해결하기 위해 프로그래머가 할 수있는 일을 어떻게 제한합니까?

복잡성

복잡성은 시간이 지남에 따라 커집니다. 간단한 시스템으로 시작하는 것은 시간이 지남에 따라 복잡한 시스템으로 발전 할 것입니다. 복잡한 시스템으로 시작하는 것은 시간이 지남에 따라 혼란으로 발전 할 것입니다.

그렇다면 프로그래머가 복잡성을 줄 이도록 어떻게 제한합니까?

우선 프로그래머가 완전히 분해 된 코드를 작성하도록 강요 할 수 있습니다. 완전히 불가능하지는 않지만 어려운 일이지만,이 행동을 장려하고 보상하는 언어를 만들 수 있습니다.

많은 함수형 프로그래밍 언어, 특히 순수한 언어는이 두 가지를 모두 수행합니다.

계산 인 함수를 작성하면 사물을 매우 분해 된 방식으로 작성해야합니다. 또한 문제의 정신적 모델을 통해 생각하도록 강요합니다.

또한 프로그래머가 자신의 기능에서 수행 할 수있는 작업, 즉 모든 기능을 순수하게 만드는 데 제한을 둘 수 있습니다. 순수 함수는 부작용이없는 함수입니다. 즉, 함수는 외부에있는 데이터에 액세스 할 수 없습니다.

순수 함수는 전달 된 데이터 만 처리 한 다음 결과를 계산하여 반환합니다. 동일한 입력으로 Pure Function을 호출 할 때마다 항상 동일한 출력을 생성합니다.

이렇게하면 순수한 함수에 대한 추론이 순수하지 않은 것보다 훨씬 쉬워집니다. 가능한 모든 기능이 함수 내에 완전히 포함되어 있기 때문입니다. 자체 포함 된 단위이므로보다 쉽게 ​​단위를 테스트 할 수 있습니다. 계산이 비싸면 결과를 캐시 할 수 있습니다. 입력이 동일하면 출력이 항상 동일하다는 것을 알고 있습니다. 캐시에 대한 완벽한 시나리오.

함수를 로컬 함수로만 사용할 수 있고 개발자가 솔루션을 자연스럽게 분해 할 수 있으므로 프로그래머가 순수 함수로만 제한하면 복잡성이 크게 제한됩니다.

재사용 성

업계는 프로그래밍이 진행되는 한이 문제와 씨름하고 있습니다. 먼저 우리는 라이브러리와 구조화 된 프로그래밍 그리고 객체 지향 상속을 가졌습니다.

이러한 모든 접근 방식은 호소력과 성공이 제한적입니다. 그러나 거의 모든 프로그래머가 항상 작동하고 사용하는 방법 중 하나는 복사 / 붙여 넣기 (일명 카피 파스타)입니다.

코드를 복사하여 붙여 넣는 경우 잘못되었습니다.

프로그램을 텍스트로 작성하는 한 복사 / 붙여 넣기에서 프로그래머를 제한 할 수는 없지만 더 좋은 것을 줄 수 있습니다.

함수형 프로그래밍에는 copypasta, 즉 z보다 훨씬 나은 표준 기능이 있습니다. 고차 함수, 카레 및 구성.

고차 함수를 사용하면 프로그래머가 데이터 및 함수 인 매개 변수를 전달할 수 있습니다. 이를 지원하지 않는 언어에서 유일한 해결책은 함수를 복사하여 붙여 넣은 다음 논리를 편집하는 것입니다. 고차 함수를 사용하면 논리를 함수 형태의 매개 변수로 전달할 수 있습니다.

Currying을 통해 매개 변수를 한 번에 하나씩 함수에 적용 할 수 있습니다. 이를 통해 프로그래머는 일반화 된 버전의 기능을 작성한 다음 일부 매개 변수를 "구워"더 전문화 된 버전을 작성할 수 있습니다.

컴포지션을 통해 프로그래머는 Legos ™와 같은 기능을 조합하여 데이터가 한 기능에서 다음 기능으로 흐르는 파이프 라인에 내장 된 기능을 재사용 할 수 있습니다. 유닉스 파이프는 단순한 형태입니다.

따라서 copypasta를 제거 할 수는 없지만 언어 지원 및 코드 기반에서이를 허용하지 않는 코드 검토를 통해 불필요하게 만들 수 있습니다.

글로벌 가변 상태

이것은 대부분의 사람들이 모르는 프로그래밍에서 가장 큰 문제 일 것입니다.

컴퓨터를 다시 부팅하거나 문제가있는 응용 프로그램을 다시 시작하여 글리치를 프로그래밍하는 대부분의 솔루션이 수정 된 이유가 궁금하십니까? 그것은 국가 때문입니다. 프로그램 상태가 손상되었습니다.

프로그램 어딘가에서 상태가 유효하지 않은 방식으로 변경되었습니다. 이들은 해결하기 가장 어려운 버그 중 일부입니다. 왜? 그것들은 재생산하기가 어렵 기 때문입니다.

안정적으로 재현 할 수없는 경우 실제로 수정했는지 알 수 없습니다. 수정 사항을 테스트해도 문제가 발생하지 않습니다. 하지만 당신이 고쳤거나 아직 일어나지 않았기 때문입니까?

상태를 올바르게 관리하는 것이 안정성을 보장하기 위해 프로그램에서 수행 할 수있는 가장 중요한 일입니다.

함수형 프로그래밍은 언어 수준에서 프로그래머를 제한하여이 문제를 해결합니다. 프로그래머는 가변 변수를 작성할 수 없습니다.

처음에는 너무 멀리 떨어진 것처럼 보일 수 있으며이 내용을 읽을 때 갈퀴를 갈고 갈 수 있습니다. 그러나 실제로 이러한 시스템으로 작업 할 때 모든 데이터 구조를 변경할 수없는 상태를 유지하면서 상태를 관리 할 수 ​​있음을 알 수 있습니다. 즉 변수에 값이 있으면 절대로 변경할 수 없습니다.

이것은 상태가 변할 수 없다는 것을 의미하지는 않습니다. 단지 그렇게하려면 현재 상태를 새로운 상태를 생성하는 함수로 전달해야합니다. 비트 트위 커가 다시 갈퀴를 꺼내기 전에 구조 공유를 통해 커버 아래에서 이러한 작업을 최적화하는 메커니즘이 있다는 것을 확신 할 수 있습니다.

표지 아래에는 돌연변이가 발생합니다. GOTO가 제거 된 예전처럼 컴파일러 나 런타임은 여전히 ​​GOTO를 수행합니다. 프로그래머에게는 제공되지 않습니다.

부작용이 발생해야 할 때 기능 언어는 잠재적으로 위험한 프로그램 부분을 포함하는 방법을 갖습니다. 좋은 구현에서, 코드의이 부분들은 명백하게 위험하고 순수한 코드와 분리되어 있습니다.

또한 코드의 98 %가 부작용이없는 경우 상태를 손상시키는 버그는 2 % 만있을 수 있습니다. 이것은 위험한 부분들이 서로 연관되어 있기 때문에 프로그래머가 이런 종류의 버그를 찾을 수있는 기회를 제공합니다.

따라서 프로그래머를 순수한 기능으로 만 제한함으로써보다 안전하고 안정적인 프로그램을 만들 수 있습니다.

동적 타이핑

정적 타이핑과 동적 타이핑에 대한 또 다른 길고 오래된 전투가 있습니다. 정적 입력은 컴파일 타임에 변수 유형이 확인되는 곳입니다. 유형을 정의하면 컴파일러가 올바르게 사용하는지 확인할 수 있습니다.

정적 타이핑에 대한 논쟁은 프로그래머에게 불필요한 부담을주고 자세한 타이핑 정보로 코드를 버리게된다는 것입니다. 이 유형 정보는 함수의 사양과 일치하기 때문에 구문 상 노이즈가 있습니다.

동적 입력은 컴파일 타임에 변수 유형을 지정하거나 확인하지 않는 곳입니다. 실제로 동적 입력을 사용하는 대부분의 언어는 컴파일 된 언어가 아닙니다.

동적 입력에 대한 논쟁은 코드를 크게 정리하는 동안 변수의 모든 오용은 프로그래머가 잡을 수 없다는 것입니다. 프로그램이 실행될 때까지 잡히지 않습니다. 이는 모든 최선의 노력에도 불구하고 유형 버그로 인해 프로덕션 환경으로 전환 될 수 있음을 의미합니다.

그렇다면 어느 것이 가장 좋습니까? 여기서 프로그래머 제한을 고려하고 있기 때문에 단점에도 불구하고 정적 입력에 대한 논거를 기대할 수 있습니다. 글쎄요,하지만 두 세계를 모두 이용할 수 있다면 좋지 않을까요?

결과적으로 모든 정적 입력 시스템이 동일하게 생성되는 것은 아닙니다. 많은 함수형 프로그래밍 언어는 컴파일러가 사용자가 작성하는 함수 유형을 사용 방식에 따라 유추 할 수있는 유형 유추를 지원합니다.

즉, 지정하는 모든 오버 헤드없이 정적 입력을 할 수 있습니다. 모범 사례에 따르면 유추와 달리 타이핑을 지정해야하지만 Haskell 및 Elm과 같은 언어에서는 타이핑 구문이 눈에 거슬리지 않고 매우 유용합니다.

정적으로 유형이 지정되지 않은 작동하지 않는 언어, 즉 명령형 언어는 프로그래머에게 투자 수익이 거의없는 유형을 지정하는 데 부담을주는 경향이 있습니다.

반대로 Haskell과 Elm의 타입 시스템은 실제로 프로그래머 코드를 개선하고 프로그램이 제대로 작동하지 않을 때 컴파일 타임에 알려줍니다.

따라서 프로그래머는 정적 입력을 양호하게 제한함으로써 컴파일러는 실제로 버그를 감지하고 유형을 유추하며 개발자에게 자세한 정보를 제공하는 데 도움을주는 대신 코드를 지원할 수 있습니다.

테스팅

테스트 코드를 작성하는 것은 현대 프로그래머의 존재를 넘어선 것입니다. 개발자가 테스트하는 원래 코드보다 테스트 코드를 작성하는 데 많은 시간이 소요되는 경우가 많습니다.

자동화가 불가능하지는 않지만 데이터베이스 나 웹 서버와 인터페이스하는 기능에 대한 테스트 코드를 작성하는 것은 어렵습니다. 일반적으로 두 가지 옵션이 있습니다.

  1. 테스트를 작성하지 마십시오
  2. 데이터베이스 또는 서버 모형

옵션 # 1은 분명히 좋은 옵션은 아니지만 테스트하려는 모듈을 작성하는 데 걸리는 시간보다 복잡한 시스템을 조롱하는 데 시간이 더 걸릴 수 있으므로 많은 사람들이이 길을 택합니다.

그러나 코드를 순수 함수로 제한하면 부작용이나 돌연변이가 발생하기 때문에 데이터베이스에 직접 인터페이스 할 수 없습니다. 우리는 여전히 데이터베이스에 액세스해야하지만, 이제는 대부분의 모듈을 순수하게 남겨 두는 매우 얇은 인터페이스 계층으로 위험한 코드 계층을 갖습니다.

순수한 기능을 테스트하는 것이 훨씬 쉽습니다. 그러나 우리는 여전히 존재하지 않는 테스트 코드를 작성해야합니다. 아니면 우리?

기능 프로그램을 자동으로 테스트하는 프로그램이 있음이 밝혀졌습니다. 프로그래머가 제공해야 할 유일한 것은 함수가 준수해야 할 속성입니다. 역함수 란? Haskell 자동 테스터를 QuickCheck라고합니다.

따라서 대부분의 기능을 순수하게 제한함으로써 테스트가 훨씬 쉽고 때로는 간단한 경우가 있습니다.

무어의 법칙

무어의 법칙은 실제로 법이 아니라 컴퓨팅 성능이 2 년마다 두 배로 늘어날 것이라는 관찰에 더 가깝습니다.

이것은 50 년 이상 사실이었습니다. 그러나 슬프게도 현재 기술의 한계에 도달했습니다. 또한 비 실리콘 기반 기술을 개발하여 컴퓨터를 구축하는 데 수십 년이 걸릴 수 있습니다.

그때까지 컴퓨터 속도를 두 배로 늘리는 가장 좋은 방법은 코어, 즉 CPU의 컴퓨팅 엔진 수를 두 배로 늘리는 것입니다. 문제는 하드웨어 제조업체가 더 많은 코어를 제공 할 수 없다는 것이 아닙니다. 문제는 배터리와 소프트웨어입니다.

컴퓨팅 파워를 배가시키는 것은 CPU가 소비하는 파워를 배가시키는 것을 의미합니다. 이렇게하면 오늘날보다 배터리가 더 빨리 소모됩니다. 배터리 기술은 만족할 수없는 사용자의 식욕보다 훨씬 뒤떨어져 있습니다.

배터리를 소모하기 위해 코어를 추가하기 전에 이미 가지고있는 코어의 사용을 최적화해야합니다. 이곳은 소프트웨어가 들어오는 곳입니다. 현재, 명령형 언어는 프로그램을 병렬로 실행하기가 매우 어렵습니다.

오늘날 그렇게하려면 개발자가 부담해야합니다. 프로그램은 얇게 썰어 평행 한 부분으로 절단해야합니다. 이것은 간단한 작업이 아닙니다. 실제로 JavaScript와 같은 언어를 사용하면 프로그래머가 코드를 병렬로 실행할 수 없기 때문에이를 제어 할 수 없습니다 (예 : 단일 스레드).

그러나 Pure Functions를 사용하면 실행 순서가 중요하지 않습니다. 중요한 것은 입력을 사용할 수 있다는 것입니다. 이는 컴파일러 또는 런타임 시스템이 실행할 함수와시기를 결정할 수 있음을 의미합니다.

함수를 순수 함수로 제한함으로써 프로그래머는 병렬 처리에 대해 걱정할 필요가 없습니다.

기능적 프로그램은 개발자에게 복잡성을 추가하지 않고도 멀티 코어 머신을 더 잘 활용할 수 있어야합니다.

적은 비용으로 더 많은 일을

우리가 보았 듯이, 적절하게 제한을 가하면 예술, 디자인 및 생활을 크게 향상시킬 수 있습니다.

하드웨어 엔지니어는 도구의 자연적인 한계로 인해 작업이 쉬워지고 지난 수십 년 동안 큰 발전을 이룰 수있었습니다.

소프트웨어 엔지니어 인 우리도 적은 비용으로 더 많은 일을 할 수 있도록 스스로 한계를 정해야 할 때가 아닙니다.

이것을 좋아한다면 아래의 을 클릭하면 다른 사람들이 이것을 Medium에서 볼 수 있습니다.

Elm에서 Functional Programming을 사용하여 서로 배우고 웹 앱을 개발하도록 돕는 웹 개발자 커뮤니티에 참여하려면 내 Facebook 그룹을 확인하십시오. Elm Programming 알아보기 https://www.facebook.com/groups/learnelm/

내 트위터 : @cscalfani