Chienomi

プログラミングは難しい? できるといいことがある?

プログラミング::beginners

「プログラミングは私にはできない」「プログラミングはとても難しそう」

私がよく聞く言葉だ。

私はプログラミング未経験者に向けた記事を色々書いている。 だが、これらは基本的に「ちゃんと」プログラミングできるようになるためのもの、あるいはプログラミングを正しく認識するためのものである。

今回は、「もっとやる気のないテキトーに言ってる人に向けてカジュアルな内容を」提供する。

この記事を書くモチベーションは、Mimir Yokohamaを含め私のところに来る「プログラミング入門記事」のニーズは、圧倒的に「プログラミングできるようになれば仕事につける」という考えからで、「仕事の話」に過ぎないことがほとんどである、ということにある。 この記事を求めている人はそのような下心ありきではなく、より漠然とプログラミングについて述べている状態であり、そういう人に説明するのは「業務の話」をするよりもずっと楽しいからだ。

もし前提としてあなたが「プログラミングができるようになりたい」という明確な意思を持っているのであれば過去の記事のほうを読むといい。

プログラミングの実績解除

まずは「プログラミングをやったことない」というあなたの人生の実績を解除しておこう1

まずはウェブブラウザ(Chromium(=Google Chrome)あるいはFirefox)で適当なウェブページを開く。

「適当な」と言ったが、最近のウェブサイトはだいたいいらんことをしてエラーを出しまくっているので、エラーの出ないChienomiを開くことをおすすめする。

次にF12、またはCtrl+Shift+Iを押す。 Macの場合は「⌘ + ⌥ + I」らしい。私は読むことすらできない。なにこれ。

するとこんな感じの画面が出る。

ウェブインスペクタ

ここでConsoleを押すと次のようになる。

ウェブインスペクタのConsole

Chienomiであればエラーは出ないはずだが、状況によってはその他のメッセージ(例えば言語パックのメッセージ)が出ることがあるほか、拡張機能をインストールしていると大量のエラーが出たりすることもある。 ちなみに、VivaldiはVivaldiの機能自体にエラーが出たりするため、素直に拡張機能の入っていないChromiumを使うのが一番おすすめではある。が、Google ChromeかFirefoxなら細かいことを気にしなければそれでいい。

そしたら>のところにフォーカスして次のように入力する。 「プログラミングをした」という実感のために、コピペせずに入力することをおすすめする。

console.log("Hello, world!")
Hello, world!

おめでとう。 これであなたは「プログラミングをする」という人生の実績を解除した。

「プログラミング」の俯瞰

言いたいことは分かる。 「違うそうじゃない」だろう。

そう思う人は、「プログラミング」ということに対して固定観念を持ちすぎているのだ。

まずは「プログラミング」という語に着目してみよう。 この語は「プログラム」の動名詞であることは一目瞭然だが、一般語としての「プログラム」は名詞である。 これが計算機の領域「プログラムを作る」という動詞化がなされ、それが動名詞になって「プログラミング」になっているわけだ。

つまり、プログラミングとは行為である。

じゃあ計算機における「プログラム」(名詞)とは何かという話だが、これはちょっと難しいことになる。

というのも、もともとは計算機は「プログラムを実行するための機械」であった。よってコンピュータを動作させるための行為は須らくプログラミング(とプログラムの実行)であった。

だが、「対話的プログラム」が登場することによって、プログラミングではないコンピュータを操作する行為が誕生した。 このために「境界」がものすごく難しくて、プログラミングの定義を一意にしづらいのだ。 なおかつ、プログラミング経験の浅い人が考えるほどその境界は深くない。

例えば、コマンドというものがある。 これはコマンドラインシェルに入力することでシェルプログラムを動作させ、間接的にコンピュータを動作させるものである。 例えばLinuxの標準的環境においては

ls

と入力することでファイルを一覧することができる。

この「コマンド」を「プログラミングである」と見做すことはほとんどないが、では次のコマンド

print -l ${(f)"$(print -l ***/*.(jpg|jpeg|JPG|png) | sed 's:/[^/]*$::' | sort -u)"}

は「プログラミングではない」と言って良いだろうか?

さらには、Perlというプログラミング言語があるが、Perl(の公式処理系)はコマンドとして使うことができるようになっている。 そのPerlをコマンドとして使った

ls -tr --time=mtime | perl -ne '/^[0-9a-f]{5,}/ && print;'

は「プログラミングではない」だろうか?

一般的にプログラミングというのは

  • 人間に可読な文字で記述し
  • 処理系という解釈機を通して
  • コンピュータに指示を与える

ものを指すが、これを定義にすると難しくなるのが、例えばHTMLである。

HTMLは本質的に文書であり、章立て、強調、箇条書き、表といった構造化のなされたドキュメントである。 文書作成を「プログラミング」と呼ぶことは基本的にないと思うが、一方でHTMLはウェブレンダリングエンジンを介して画面の描画処理を行うという意味で、前述の定義を満たしてしまう。

<html>
  <head><title>最初のページ</title></head>
  <body>
    <h1>見出し</h1>
    <p>Hello, world!</p>
  </body>
</html>

「プログラム」と「やりたいこと」

だがほとんどの場合、何をプログラミングに含み、何をプログラミングに含まないかということを「ちゃんと定義しようとする」のは意味がない。

プログラミングはコンピュータを活用するための「手段」に過ぎないからだ。

例えば私は家計簿をつけるためのユーティリティを書いて公開しているが、多くの人は同様の目的を達成するためにMicrosoft Excelを使うことだろう。 私は表計算ソフトウェアなんて絶対に使いたくないので、同様の目的を達成するためにコードを書くという手段を選択した。それだけの話だ。

なぜ冒頭のプログラムであなたが「そうじゃない」と感じたか。 それは「プログラムがあなたの期待に沿っていなかったから」だ。

見失いがちな本質だが、「達成したいことがあり、それを実現するための手段としてプログラミングをする」のである。 プログラミングをするそもそもの動機として、「なにをしたい」というのがないとそもそも成立しづらいのだ。

そして、期待外れになってしまうのは、漠然とした「プログラム」に対するイメージが普段その人が「プログラム」と認識して触れているものに結びついており、「プログラミングをする」という結果が「普段触っているようなプログラムが出来上がる」という期待になってしまっており、その期待が過剰であるためである。

ちなみに、プログラミングという行為自体が楽しいものであるため、プログラミングするようになると別に達成したいことがあるわけではないが、美しいコードや複雑なコードを書くことに喜びを見出すようになったりもする。 こういう方向に傾く人であれば、競技プログラミングの世界が合っているかもしれない。

「やりたいことを達成する」という意味でプログラミングの本質を考えると、私は「コマンドから始めよ」と言いたい。

例外は多数あるが、単一のコマンドでは通常「なにを動かすか」の指示であり、「どう動かすか」の指示は含まれないためにプログラミングらしさに欠けるだろう。 だが、これが複数のコマンドの羅列であったり、コンプレックスコマンドであったりすると「端的なプログラム」になる。

例えば本記事で使われている画像は幅500pxのAVIF形式だが、もとは任意のサイズで撮ったスクリーンショットであり、PNG形式である。 これをリサイズしてAVIF形式で変更するのに

mogrify -resize 500x *.png
for i in *.png
do
  avifenc $i ${i:r}.avif
done

というコマンドを実行している。

シンプルコマンド(mogrify)とコンプレックスコマンド(for)の2つのコマンドからなり、コンプレックスコマンドは1つのシンプルコマンド(avifenc)を内包する。 つまり、登場するコマンドは3つで、これで「幅500pxのAVIF画像に変換する」という目的を達成している。

合う合わない、できるできない

プログラミングをすること自体は全く難しくないのだが、続けることはそうとは限らない。 プログラミングには明確な「向き不向き」が存在するからだ。

基本的には自分が書いたコードにしたがってコンピュータが動作することに興奮を覚えない人はプログラミングに向いておらず、プログラミングをしなければならないとなると苦痛を感じるだろう。 逆にテンションが上がりまくる人はハマってしまう可能性が高い。

そしてプログラミングで「できるできない」の話をするならば、それはプログラミングの技術だけでは決まらない。

プログラミングの中だけに閉じたプログラミングの技術というのもあるにはあるのだけど、基本的にプログラミングで使用する技術はプログラミングの外にある。 それはソフトウェアに関する知識であったり、コンピュータテクノロジーに関する知識であったり、数学であったり科学であったりする。 やろうとしていることの領域に対する知識が揃っていないと、やろうとしていることを達成するプログラムは書けない。

逆に言えば、単純にプログラミングそのものの技術というのはかなり幅が狭い。 慣れてしまえばものにもよるが基本的な言語仕様などというものは数日あれば覚えられる程度のものである。

では「プログラミング技術」の差はどこで出てくるかといえば、基本的にはコンピュータに関する全般的な知識量と理解の深さ、ひいては数学などコンピュータ外部に至る学問である。

プログラミングにおいて文系知識が役に立たないなどということはない。 例えば私がやっている仮名漢字変換辞書生成の開発は、非常に専門的なレベルで日本語学(品詞分類)の知識を要求される。

業務用プログラムを作るときは、その業務の専門知識がなければ成立しないこともある。

結局書けるプログラムは自身の知識の範疇になるため、多くを知り、多くを理解すればそれだけ幅広いプログラムが書ける。 逆に言えば、プログラミングだけを見ている人に大したプログラムは書けない。

手順と形式と方法

さて、少し立ち戻ってプログラミングという行為の具体的な内容を見てみよう。

基本的にはプログラミングの中心となる行為は、「エディタにソースコードを書く」というところにある。 別にWindowsに標準で付属している「メモ帳」を用いてプログラミングをすることもできるが、非常にやりづらいため、まずはプログラミングに適したエディタとフォントを入手するところから始まる。

テキストエディタを用意したら、任意のプログラミング言語で書いていく。 この「任意のプログラミング言語」の内容は後述するプログラミング処理系に依存する。

書き終えたら任意のプログラミング言語に即した任意のプログラミング処理系を通す。 プログラミング処理系は書いたコードを変換するなり実行するなりといったことの任意数のフェーズを行う。 まぁ、プログラミング処理系を起動したらコードの実行までやってくれる処理系を通すのがわかりやすい。

基本的には「書いたコードは直接は動かないので、処理系を通すことで動作する」という理解で良い。 このため、処理系を通す手順はあくまで実行に至る手順でしかなく、プログラミングという行為の意味合いは薄い。

冒頭の内容では「処理系を通す」という手順がなかったが、これはウェブインスペクタのConsoleはREPLという「対話的に書いたコードを逐次処理する」機能を用いたためである。 つまり、Consoleを開いた時点で既に処理系が待ち受けていたわけだ。

「プログラミングで実際どんなコードを書くのか?」ということについては、Chienomiのプログラミングカテゴリを見るのが手っ取り早い。 実践的なものに興味があるなら私のGitHubリポジトリでも見ればいい。

「役に立つ」の難しさ

私がNewbieによく言うのは、「自分がしたいこと、していることをやってくれるコードを書きなさい」ということである。

それがモチベーションになるだけでなく、そのコードの「適切さと良さ」を自身が適切に評価しやすい。コードを書くことに価値は乏しく、それによって得られるものが価値の中心であるため、「したいことを実現できるように努力する」ことはプログラミング関連技術向上に直結する。

また、そうしたコードは時間を置いて同様の課題に取り組んだときに、コードの違いから自身がどのように変化・成長したのかを客観的に確認できるという点も大きい。

しかし、プログラミングを業務にしているような人であっても、これが達成できない人は非常に多い。 これは、「何をしなさい」と条件を与えられればそれを満たすコードは書けるが、自身で何かを達成しようと考えることができないのであったり、あるいは(業務ですべてを一人で書くことは少ないため)すべてを設計して実装することができないであったりする。

また実際には達成したいことが存在する場合であっても、それを「プログラミングによって」という発想が生まれないということもある。 これはプログラミングの捉え方の問題でもある。

プログラミングを身につけることは、コンピュータによって達成する手段をひとつ増やすことである。そして、その手段は割と強力である。

増えた手段を活用する余地は、当然ながらあなたが興味のあること、あるいは得意なことに対して発揮される。 文系領域の知識が必要なプログラミングは、基本的に文系領域の知識を持っている人しか必要だと思わないため、「プログラミング単独の技術でできることが乏しい」という問題は、実際にはあまり問題にならない。 苦しいのは、達成したいことの対象について部分的な知識しかないときだ。

実際の「ちょっとしたコード」

私がその場で使うためだけにパッと書いたコードをいくつか見せよう。 言語については、私は基本的にRuby、またはZshで書く。

最初のコードはこちらだ。

#!/bin/zsh

find . -name '[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9].*' | while read
do
  mv -v $REPLY ${REPLY:h}/${${REPLY:t}[1,4]}-${${REPLY:t}[5,6]}-${${REPLY:t}[7,-1]}
done

私は写真を取り込んだとき、年-月-日.デバイス名というルールでフォルダに名前をつけているのだが、年月日.デバイス名という名前の付け方をしてしまっていたものが結構あった。 例えば、2024-10-15.x6であるべきところ、20241015.x6にしてしまっていたのである。

これを本来の名前に一括で修正するためのスクリプトである。 Zshで書かれている。 非常に汚いが、[0-9]などはコピペで簡単に増やせるため、書き捨てのコードならこういう書き方をしたほうが良い。

書き捨てでない場合にこうした書き方をしないほうが良いのは、後で読んだときに読みづらいからだ。

次のコードはこちら。

#!/bin/ruby

fhd = 1920 * 1080
uhd = 3840 * 2160
wqhd = 2440 * 1440
uwqhd = 3440 * 1440

fhd60 = (fhd * 60).to_f

def uniti i
  i.to_s.reverse.each_char.each_slice(3).map {|i| i.join }.to_a.join(",").reverse
end

data = [
  ["FullHD@60Hz", fhd * 60],
  ["WQHD@60Hz", wqhd * 60],
  ["UWQHD@60Hz", uwqhd * 60],
  ["4k@60Hz", uhd * 60],
  ["FullHD@144Hz", fhd * 144],
  ["FullHD@165Hz", fhd * 165],
  ["FullHD@500Hz", fhd * 500],
  ["FullHD@60Hz x2", fhd * 60 * 2],
  ["FullHD@60Hz x3", fhd * 60 * 3],
  ["4k@60Hz x2", uhd * 60 * 2],
  ["4k@60Hz x3", uhd * 60 * 3],
  ["WQHD@165Hz", wqhd * 165],
  ["UWQHD@144Hz", uwqhd * 144],
  ["4k@144Hz", uhd * 144],
  ["4k@60Hz x2 + FHD@60Hz", uhd * 60 * 2 + fhd * 60],
  ["4k@60Hz x2 + FHD@165Hz", uhd * 60 * 2 + fhd * 165],
  ["4k@60Hz x2 + UWQHD@165Hz + FHD@144Hz", uhd * 60 * 2 + uwqhd * 144 + fhd * 165],
  ["8k@60Hz", uhd * 60 * 4],
  ["16k@60Hz", uhd * 60 * 4 * 4]
]

data.each do |i|
  printf("|%s|%s|%.3f|\n", i[0].gsub('@', '\\@'), uniti(i[1]), i[1] / fhd60)
end

ちょっと長いが、大部分はデータであり、計算処理は最後のdata.each部分だけだ。 これは以前の記事でディスプレイ構成ごとの秒間描画ピクセル数を表にしたが、電卓で計算して表をつくる、みたいなことをすると非常に面倒なので、コードを書いて一気に生成した。

3つ目はこちら

#!/usr/bin/ruby

encoded = {}
Dir.glob("#{ARGV[1]}/**/*.webm").each do |i|
  name = File.basename(i).delete("^0-9")
  encoded[name] = i
end

Dir.glob("#{ARGV[0]}/**/*.mp4").each do |i|
  name = File.basename(i, ".mp4").sub(/.*?_/, "").delete("^0-9")
  if encoded[name]
    puts "#{i} => #{encoded[name]}"
    if ARGV[2] == "r"
      File.delete i
    end
  end
end

これは、MP4の動画ファイルをWebMに変換するという処理をしているのだが、変換済みのものとそうでないものがごっちゃになってしまったため、変換済みの動画のMP4ファイルを検出・削除するためのものだ。

これはファイルの生成のされ方と命名規則が「わかっている」ためにピンポイントでそのケースに適用できる書き方をしており、汎用性はない。

ちなみにやっていること自体は非常に簡単なことであるため、同様の目的を達成するコードを書けるようになるために必要な努力はそれほど多くない。 また、書き捨てコードなので、コード品質も別に高くない。

だが案外、これをできるようになるのは難しいものなのだ。 それは「やってみればわかる」。 必要でないものを書かないというのも、経験が必要だ。

プログラミングできることの価値

私の場合はプログラミングによって対価を得ていたりするけれど、そうした面を除いたとしてもプログラミングスキルの価値は非常に大きいと断言できる。

プログラミングスキルは生産性に寄与する。 つまり、プログラミングをすることで、時間を短縮したり、ストレスを感じる作業を減らしたりできる。

両方とも効果は非常に大きい。 というか、プログラミングせずに全部手作業でやったとすると何年もかるかような作業もあったりする。つまりはそのようなことはプログラミングができなければ諦めるしかないことが、プログラミングができることでいともたやすく実現される。

プログラミングによってお金の節約ができるというのもある。 圧縮や重複排除などは手作業では難しいものも多いので、基本的にストレージにお金を払うことになるが、データサイズが大きいとえげつない出費になるので、これを抑制できるのは非常に大きい。

また、数量限定セールのような、「ごく短時間だけ安く買える」というような状況を察知できるようにして節約することもある。

同様の方法で「早いもの勝ち」系もプログラムを組んでいると強い。 というか、場合によっては確保するところまで書いてしまうこともあり、人力では勝てないと思ったほうが良い。

通知関連は価値を計れないものが多い。 1秒でも早く知るべき情報をメッセンジャー(私はMattermostとTelegramを使っているけれど、一般的にはLINEだろう)で通知するようにしているのはかすなり大きな意味を持っている。

また、情報を要約して見やすいようにするというための使うこともある。 いちいち情報を探す必要もなく、確認したいときに確認したい情報がすぐそこにあるというのはとても便利だ。

結論

  • プログラミング自体は難しいことではない
  • 純粋にプログラミングの技術というものもあるが、実際はコンピュータに関わる知識や技術への依存度が大きい
  • 実用的なものを作るには、作ろうとするものの領域の知識や関心が非常に重要
  • プログラミングができることで発想次第で日常的にすごく便利になる
  • あなたもやろうプログラミング