見出し画像

Qiitaひとりアドカレ供養「技術記事はつらいよ」

⚠️闇駆動執筆でひとりアドベントカレンダーを仕上げた筆者の、余ってしまった闇部分をうまくオブラートに包んで浄化した(?)のが本記事です。たまに闇が暴走して読者に不快な思いをさせてしまう内容があるかもしれませんが、特定の個人・団体を誹謗中傷する意思は一切なく、筆者が嘆く事象の原因はすべて筆者の自業自得であることはしっかり認識していることをここに強く記しておきます。こんな前置きが長い記事ですが良かったらどうぞ

2025年4月から11月末にかけて、Rust言語のとても便利なマクロhooqを開発してきました!

しかしこれが全然スターがつかない!ダウンロードも全然されない…悲しい…

ということで、「hooqの技術記事を増やして宣伝をすればよいのでは?」と考え、12月から「hooqアドベントカレンダー」と銘打ってひとりアドベントカレンダーを走り始めました!

https://qiita.com/advent-calendar/2025/hooq

しかしこちらも全然いいねがつかない!ほぼオンスケジュールで遅れることがあっても1日という、夏休みの宿題は最終日までダラダラやって結局夏休み明けまで終わらない筆者にしてみれば人生最大の快挙を25日まで続けたというのに、世間は全く見向きもしてくれませんでした…(この期間にいいねをくれた方は本当にありがとうございます 🙇‍♂️ )

そんなわけで、本記事ではこのアドカレを振り返ることで自分を慰めたいと思います!

年始一発目書初めの記事が昨年のアドカレを振り返る内容であるというのはなんとも歯がゆいものですが、人生キリ良く進むものではないと思うのでこのまま書きます。年始一発目の記事がグラビアアイドルについてというすごい技術者の方もいますし多分普通


hooqアドカレのこだわり・反省・愚痴

正直2025年Qiitaひとりアドベントカレンダーの中でもかなり珍しいアドカレだと自負しています。下記をすべて満たしているような記事自体Qiitaだと最近はレアではないでしょうか?

  • 1. Rustに関する硬派な記事多め

    • 「属性マクロ」と「エラーハンドリング」なので分野的にはニッチかもしれませんが、個人的な経験だけではなく一般に応用が利きそうな内容も多め

  • 2. 全部技術記事

  • 3. 文章はノー生成AI

    • 文章は全部手打ちです

    • とはいえ生成AIも一部活用はしています

      • レビュー

      • 画像生成

    • 生成AIでの記事生成も試してはみたのですがつまらん!ハルシネーション多いし…

しかし多分1つめと2つめが全くいいねがつかなかった原因です。

  • RustaceanはQiitaよりZennの方が多い

  • イマドキ技術記事にはいいねがつかない

今まではなんとなくでしたが、今回はっきり上記を体感しました。特に2番目については自分の記事以外も年々この傾向が強くなっていることを感じます。承認欲求目的でひとりアドカレを走るならば技術記事ではなくポエムアイデア記事がオススメです。偏見ですが大体は技術記事よりアイデア記事の方が楽です。他に目的がないならば技術記事で走ることは苦行以外の何物でもありません。ちなみに筆者は今回hooqをSEO優位にするという目的で走っていたので闇堕ち寸前で止まれました。この目的は一応達成されています。多分。



…え?「いいねがつかないのは言いがかりだろう。もっと根本的な原因があるだろう」ですって…?そうですね…正直責任転嫁の八つ当たりです。真面目に反省すると以下の方がよほど原因だと思います。

  • hooqとかいう未知のマクロについての記事

    • そもそもやっぱり世間はhooqに興味がない

  • そもそもひとりアドベントカレンダーは流入量が少ない

  • アドベントカレンダーとかいう技術記事がたくさん投稿される時期に記事を投稿する愚行

  • etcetc…

完全に自業自得ですよハイ…この節で言いたかったのは「アイデア記事の方が記事マラソンで楽」ということではないです。「技術記事でアドカレを走ると筆者みたいに闇を抱えてしまい泣きながら年末を迎える走者が出てくるかもしれない」ということが言いたいのです。Qiitaさんはぜひこちらの問題に向き合ってくださると技術記事が好きな筆者としてはとても報われる思いです。

生成AI記事について等、まだ思うところがありますが、これ以上の闇は「まとめ・所感」にて吐き出します。

…まぁ、これ以上嘆いてもしょうがないです!ともかく、結構頑張った記事が多いということで、自分で自分の記事を讃えていきたいと思います。

以下色々なランキングです!2026年1月2日20時時点までの記録を取っています。なお、hooqアドベントカレンダーに紐づかない以下3本の記事も一応関連記事なので統計に含めました。

個人的お気に入り記事紹介

全記事本気で書きましたしお気に入りですが、特に良かったなと思う記事群をピックアップしました!

Rustコミュニティに対し新規性がありそうな属性マクロ記事群

自分以外に同内容を書いている人は多分居なさそうな、新規性のある記事を何本か出せました!

とは言っても全部属性マクロについてですね…😅 属性マクロは情報が少なすぎるのだと思います。

hooqに全然スターがつかないことからhooq自体の需要はないんじゃないかとナイーブになっていて、それでもhooqに取り組んだ意義を見出したくメイキング記事と称して作成にあたり得たノウハウをしたためました。ひとりアドカレは走らず例年通り一記事しか出さなかった場合、多分一番上の任意位置に属性を付ける方法をアドカレ記事にしていたと思います。

エラー関連クレートの事実確認・PoCに取り組んだ記事群

「結局hooqマクロは役に立つのか?エラー関連クレートにおけるhooqの立ち位置はどこなのか?」これはアドカレを始めてから明らかにしたかった部分でした。そのPoCに取り組んだり、あやふやな知識を確かめた記事はかなり有意義なものになったと思います。特に以下2つは収穫が多かったです。

最後のまとめ記事等で「マサカリ上等!」みたいに喧嘩を売っていますが、基礎知識については普通に耐えうるぐらい身に着けられたと思います。一方で経験則的な部分は弱いので具体的なユースケースを伴ったマサカリとかには粉砕されそうですね…というかそういうのが来たらとても参考になるので普通に嬉しいです。

闇1選

【Rust】.context(...)を書くな【anyhow・eyre】

hooqにスターが全然つかなくて、それで始めたアドカレ記事にもいいねがつかなくて悪態をついた記事です。逆によくここから22記事も書いたものだなと思います。

「Rust context」でGoogle検索すると1番目にヒットするのでSEO的には成功した方だと思います。contextを書くな

いいね数ランキング

🥇1位 中途半端にResultを使うぐらいならunwrapの方がマシ - Zenn (73いいね)

🥈2位 Rustのエラークレート選定方法 - Qiita (25いいね)

🥉3位 Rustミニクイズ集 ① - Qiita (20いいね)

🎖4位 【Rust】お手軽エラーロギング用マクロ hooq を作りました! - Qiita (11いいね)

🎖4位タイ 【Rust】.context(...)を書くな【anyhow・eyre】 - Qiita (11いいね)

いいね数集計

  • Qiitaいいね数合計: 159

  • Zennいいね数合計: 79

  • 全合計いいね数: 238🎉ありがとうございます!🙇‍♂️

アドカレ終盤記事のいいね数が多いのは素直に嬉しいですね。序盤の記事も奮闘しています。

…まぁでもやっぱあんま言いたくないですがQiitaはいいねつきにくいですね…Qiitanグッズ欲しさにQiitaに投稿していましたが、アドカレじゃなかったらもう全部Zennに投稿していたと思います。もっといいね欲しかった…!!!!!

1位記事のタイトルは普通に釣りで中身はResult型を賛美しています。

総合文字数ランキング

ソースコード等すべて含めた文字数の集計です。空白文字は含みません。

🥇1位 【Rust】好きな位置に好きな属性(#[attr])を付ける方法【ハンズオン付】 - Qiita 37,058文字

🥈2位 中途半端にResultを使うぐらいならunwrapの方がマシ - Zenn 21,135文字

🥉3位 Rustのエラークレート選定方法 - Qiita 17,164文字

🎖4位 【Rust】色々あるエラークレートを触ってみる【eyre・snafu・miette】 - Qiita 15,318文字

最下位 hooqマクロの記述回数をなるべく抑えたい【タイムカプセル】 - Qiita 3,342文字

文字数合計: 308,351文字
1記事平均: 11,013文字

今回1万文字を超える記事は14本ほど出せました。文字数が多いほど良いというわけではありませんが、気を抜いた記事は少なかったんじゃないかと思います。

知人と予定があった2025/12/06以降ずっと家に引きこもって寝食を忘れ記事を書いていたのですが、その甲斐があったというものです。もう二度としない…

全角文字数ランキング

ソースコード・空白文字だけでなくアルファベット・数字もすべて抜き、全角文字数を数えたものです。全角文字の一文字判定には unicode_segmentation クレートを用いています。

🥇1位 【Rust】好きな位置に好きな属性(#[attr])を付ける方法【ハンズオン付】 - Qiita 5,912文字

🥈2位 中途半端にResultを使うぐらいならunwrapの方がマシ - Zenn 5,618文字

🥉3位 Rustのエラークレート選定方法 - Qiita 5,232文字

🎖4位 とりあえずtracingを体感する - Qiita 4,380文字

最下位 hooqマクロの記述回数をなるべく抑えたい【タイムカプセル】 - Qiita 687文字

全角文字数合計: 70,517文字
1記事平均: 2,518文字

「総合文字数ランキングはソースコード等で水増ししているんじゃないか?」と思われるのも癪なので一応出しました。全角文字だけで合計7万文字越えですよ?書きすぎ

Backtraceに向き合う① - Qiitaが一番ラフな記事になってしまったつもりだったのですが、最下位はmod全体に属性マクロを適用する方法についてで新規性を含む記事のほうでした。全部本気で書けた、ということでいいんじゃないかなと思います!

隠しネタ・隠しネットミーム

友達が少ないのでネットミーム発散欲を満たせるのは技術記事しかありません。ネットミームを書き込むために技術記事を書いていると言っても過言ではないのです。だから生成AIに記事を書かせるなんて勿体ないわけですね。

というわけで(?)アドカレ記事に仕込んだネタ・ネットミームをここでネタばらしします。自分で仕込んだネタを自分で解説させられるのはなんか敗北感ありますがせっかくなのでします。

まとめ・所感

偉業を成し遂げたものにはポエムる権利があります。以下、ポエムです。


この記事を書いたのは、SNSや生成AIの影響によりクリエーターにとって世界が「自惚れの時代」に突入したためです。

小学校の時分、「ナルシスト」や「自己中」は嫌われるとして長らく封印してきた感覚「自惚れ」。生成AIがはびこる昨今、筆者はこの感覚を自分の手元に呼び戻すことにしました。だから自分のアドカレを称賛する記事を書きました。

なぜ「自惚れの時代」に突入したのか?それはあなたの「創作物」を称賛してくれる"他者"が遠くなったからです。

人々の称賛 ―― 「いいね」の意味は変わってきています。

創作物それ自体ではなく、その経験や体験談ばかりに「いいね」が付くようになりました。「いいね」をすると読者は「その経験に称賛したもの」としてのステータスを得られます。その「いいね」は「応援したい」という純粋な気持ちから贈るものもあるでしょう。しかし、言葉を選ばず言えば、「『いいね』をした自分はこの経験を『認めた』承認者なのだ」という一種のマウンティングのため、あるいは安心を得るために贈るものになっているのではないでしょうか?つまり、経験に「いいね」を押すことには相変わらずメリットがあります。そしてこの生成AIとSNSによるメイドインヘブン状態でたくさんの「経験」が流れてくる昨今、たくさんの「いいね」を押す必要が出てきているのです。だから、アイデア記事はいまだにたくさん「いいね」をもらえます。

一方、「創作物」自体、つまり技術記事への「いいね」はハードルがかなり高くなりました。生成AI製記事の台頭により、すべての技術記事に対して「内容の精査をしなければならない」という暗黙の義務がつきまとうようになったためです。もし生成AIによるハルシネーションを含む記事に「いいね」をしてしまえば悪者になってしまいます。わからないことは生成AIに聞く時代となり、「技術記事により救われた人」からの「いいね」は減少。結果として、内容精査のプレッシャーを伴う「いいね」だけが残り、技術記事は拠り所を失っていきます。つまるところ誰も「いいね」を押したがらなくなったのです。

以上より、生成AIの時代に技術記事執筆者を始めとしたクリエーターに必要なのは、もはや手に入らない「いいね」ではなく「自惚れ」であろうと筆者は考えているのです。

そして自惚れによる自己正当化のため、人類は皆これからどんどんキモくなっていくしかないでしょう。それで良いのです。キモくなりましょう。生成AIが常に模範解答を見せながら伴走してくる時代になりました。ゆえに、AIがさらに発達し人類がスカイネットに滅ぼされるまでの間、正気を保ち続け他人の顔色を窺い、他人からの「いいね」を求め生きていくことはどんどん辛くなると思います。社会の圧に潰されないように、自分に正直になりましょう。そういう時代にすでに突入していて、技術記事の衰退はその片鱗なのだろうと筆者は考えています。

何が言いたいかというと早いところメイドロボにヨシヨシと褒められながら膝枕で寝たい、ということです。モウムズカシイコトカンガエタクナイ

というわけで各技術記事プラットフォーム様には「いいね」を押してくれるメイドロボの配備を求めます。よろしくお願いします。ここまで読んでくださりありがとうございました!

おまけ: 集計用スクリプト

今回のランキングを作成するのに用いたスクリプト群です。

いいね数ランキング用スクリプト

Qiita APIを利用しました。スクリプトで取得したのはQiita分のみです。Zennの分は記事作成時点のものを手動で得ました。

use clap::Parser;
use hooq::hooq;

#[derive(Parser, Debug)]
struct Cli {
    /// Your Qiita API Key
    #[clap(long, env = "QIITA_API_KEY")]
    qiita_api_key: String,
    /// Number of articles to fetch
    #[clap(short, long, default_value_t = 100)]
    num_articles: usize,
}

#[derive(serde::Deserialize, Debug)]
struct Article {
    id: String,
    title: String,
    likes_count: usize,
}

#[hooq(anyhow)]
async fn query(api_key: String, num_articles: usize) -> anyhow::Result<Vec<Article>> {
    let res = reqwest::Client::new()
        .get("https://qiita.com/api/v2/authenticated_user/items")
        .bearer_auth(api_key)
        .query(&[
            ("page", "1".to_string()),
            ("per_page", num_articles.to_string()),
        ])
        .send()
        .await?;

    let raw_articles: serde_json::Value = res.json().await?;

    let articles: Vec<Article> =
        serde_json::from_value(raw_articles.clone()).inspect_err(|_| {
            dbg!(raw_articles);
        })?;

    Ok(articles)
}

#[hooq(anyhow)]
#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let Cli {
        qiita_api_key,
        num_articles,
    } = Cli::parse();

    let mut articles = query(qiita_api_key, num_articles).await?;

    articles.sort_by(|a, b| b.likes_count.cmp(&a.likes_count));

    for article in articles {
        println!(
            "Likes: {}, Title: {}, URL: https://qiita.com/items/{}",
            article.likes_count, article.title, article.id
        );
    }

    Ok(())
}

以下はCargo.tomlです

[package]
name = "fav_ranking"
version = "0.1.0"
edition = "2024"

[dependencies]
anyhow = "1.0.100"
clap = { version = "4.5.53", features = ["derive", "env"] }
hooq = "0.3.1"
reqwest = { version = "0.13.1", features = ["json", "query"] }
serde = "1.0.228"
serde_json = "1.0.148"
tkio = { version = "1.48.0", features = ["full"] }

文字数ランキング用スクリプト

  • 全文字

  • ソースコード抜き

  • ソースコード抜き・全角文字のみ

上記3モードそれぞれで集計を行い、「全文字」と「ソースコード抜き・全角文字のみ」の掲載をしました。

use std::path::PathBuf;

use clap::Parser;
use hooq::hooq;
use unicode_segmentation::UnicodeSegmentation;

#[derive(clap::ValueEnum, Clone, Copy, Debug)]
enum CountMode {
    All,
    ExceptCodes,
    OnlyJapanese,
}

#[derive(Parser, Debug)]
struct Cli {
    /// 文字数カウント対象パス (glob形式)
    targets: Vec<String>,
    /// カウントモード
    #[clap(short, long, value_enum, default_value_t = CountMode::All)]
    mode: CountMode,
    /// デバッグモード
    #[clap(short, long, default_value_t = false)]
    debug: bool,
}

fn count_inner(s: &str, is_debug: bool) -> usize {
    // すべての空白文字を削除
    let s = s.chars().filter(|c| !c.is_whitespace()).collect::<String>();

    if is_debug {
        println!("{s}");
    }

    s.graphemes(true).collect::<Vec<&str>>().len()
}

fn strip_meta_section(s: &str) -> anyhow::Result<String> {
    let re = regex::Regex::new(r"(?s)^---.*?---\n")?;

    Ok(re.replace(s, "").to_string())
}

fn strip_codes(s: &str) -> anyhow::Result<String> {
    let re = regex::Regex::new(r"(?s)```.*?```")?;

    Ok(re.replace_all(s, "").to_string())
}

#[hooq(anyhow)]
fn count_all(path: &PathBuf, is_debug: bool) -> anyhow::Result<usize> {
    let content = std::fs::read_to_string(path)?;

    let content = strip_meta_section(&content)?;

    Ok(count_inner(&content, is_debug))
}

#[hooq(anyhow)]
fn count_except_codes(path: &PathBuf, is_debug: bool) -> anyhow::Result<usize> {
    let content = std::fs::read_to_string(path)?;

    let content = strip_meta_section(&content)?;

    let content = strip_codes(&content)?;

    Ok(count_inner(&content, is_debug))
}

#[hooq(anyhow)]
fn count_only_out_of_ansi(path: &PathBuf, is_debug: bool) -> anyhow::Result<usize> {
    let content = std::fs::read_to_string(path)?;

    let content = strip_meta_section(&content)?;

    let content = strip_codes(&content)?;

    let re = regex::Regex::new(r"[\x00-\x7F]")?;
    let content = re.replace_all(&content, "");

    Ok(count_inner(&content, is_debug))
}

#[derive(Debug, Clone)]
struct Article {
    path: PathBuf,
    count: usize,
}

#[hooq(anyhow)]
fn main() -> anyhow::Result<()> {
    let Cli {
        targets,
        mode,
        debug,
    } = Cli::parse();

    let mut articles = targets
        .into_iter()
        .map(|target| glob::glob(&target))
        .collect::<Result<Vec<_>, _>>()?
        .into_iter()
        .map(|paths| paths.collect::<Result<Vec<_>, _>>())
        .collect::<Result<Vec<Vec<PathBuf>>, _>>()?
        .into_iter()
        .flatten()
        .map(|path| {
            Ok::<_, anyhow::Error>(Article {
                path: path.clone(),
                count: match mode {
                    CountMode::All => count_all(&path, debug)?,
                    CountMode::ExceptCodes => count_except_codes(&path, debug)?,
                    CountMode::OnlyJapanese => count_only_out_of_ansi(&path, debug)?,
                },
            })
        })
        .collect::<Result<Vec<_>, _>>()?;

    articles.sort_by(|a, b| b.count.cmp(&a.count));

    let mut total = 0;
    for Article { path, count } in articles {
        println!("[{} chars] {}", count, path.display());
        total += count;
    }

    println!("Total: {} chars", total);

    Ok(())
}

以下はCargo.tomlです

[package]
name = "char_count_ranking"
version = "0.1.0"
edition = "2024"

[dependencies]
anyhow = "1.0.100"
clap = { version = "4.5.53", features = ["derive"] }
glob = "0.3.3"
hooq = "0.3.1"
regex = "1.12.2"
unicode-segmentation = "1.12.0"

いいなと思ったら応援しよう!

コメント

コメントするには、 ログイン または 会員登録 をお願いします。
買うたび 抽選 ※条件・上限あり \note クリエイター感謝祭ポイントバックキャンペーン/最大全額もどってくる! 12.1 月〜1.14 水 まで
Qiitaひとりアドカレ供養「技術記事はつらいよ」|namnium
word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word

mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1