LoginSignup
90
55

More than 5 years have passed since last update.

人間性をさがせよ QiitaのTypo検出

Last updated at Posted at 2018-04-10

【速報】 @khskちゃんが遂に編集リクエストの量産体制に入った

tl;dr

  • Qiitaで見かけるtypoを書き出したよ。コメントなどで協力してくれると嬉しい。
    • typoを検索結果のリンクにしたのでtypo修正編集リクエストしてみたいときの助けにしてね。
  • 普段見る記事のtypoを見つけやすいように強調するUserScriptも書いたよ。
  • もっと機械的にできるように一日分の全記事を自動でチェックするスクリプトも書いたよ。
  • typo修正はOSSでも立派なContributeだよ。
    • 新人プログラマでもとっつきやすい!

初心者の方はQiitaの編集リクエストを通してQiitaに貢献するとともにOSSへのプルリクエストの練習としてみてはいかがかな。

タイポグリセミア(Typoglycemia)

Typoglycemiaとは (タイポグリセミアとは) [単語記事] - ニコニコ大百科

と言うらしいです。
有名な

こんちには みさなん おんげき ですか? わしたは げんき です。
この ぶんょしう は いりぎす の ケブンッリジ だがいく の けゅきんう の けっか
にんんげ は もじ を にしんき する とき その さしいょ と さいご の もさじえ あいてっれば
じばんゅん は めくちちゃゃ でも ちんゃと よめる という けゅきんう に もづいとて
わざと もじの じんばゅん を いかれえて あまりす。
どでうす? ちんゃと よゃちめう でしょ?
ちんゃと よためら はのんう よしろく

というやつ。
なので大抵のtypoは、実は人間が読むだけならば直さなくてもそんなには問題無い。
だが重箱の隅を突くように検索して編集リクエストを投げつけていく!

リスト作成した!

今までは暇な時間に適当に思いついたtypoで検索して編集リクエストを出してきました。
でも毎回同じキーワードを思いついたり、「あれってもう調べ終わったっけ」と考えることが億劫になったので、リストにします。

キーワードの後ろの低・中・高はQiitaでの主観的頻出度です。
時間がないときは高だけ巡回したりするかもしれません。

投稿者としても気をつけたいものですね。

(テーブル記法の方がいいか検討中。mdの編集が辛くなる気がしてます。)

長いのでたたみます(クリックで開く)

筆者の知識・属性的にPHP・JS周辺の用語に偏重しています。HELP!

access (中)

acces
acess
aceess

action (高)

aciton
actoin

android (高)

andriod
andoroid

analyze (低)

analize

arduino (高)

ardino
aruduino
arudino

behavior (特注・高)

behaviour

※言語圏の違いによるスペルの揺れのようです。
たとえばUnityはMonoBehaviourが正です。
逆にMonoBehaviorは誤りみたいです。
Unity以外でもBehaviourが正のものがあります。
…どうしよう…Unity含め正誤がわからないので保留。

browser (低)

brawser

bootstrap(高)

boostrap
bootsrap
bootstap

bottom (中)

botton
buttom

buttonが悪い。

button (中)

botton
buttom

bottomが悪い。

bulma (低)

bluma

calendar (高)

calender

callback (低)

calback
collback

chainer (低)

chiner

channel (中)

chanel
channnel

chocolatey (低)

chocolatery

chrome (中)

chorome

chromium (低)

chronium

class (低)

calss

coffee (高)

cofee
coffe

command (中)

comand

comment (低)

coment
commnet

commit (中)

comit

communication (低)

comunication

community (中)

comunity

composer (低)

compoesr
compsoer
conposer

configure (低)

configre

console (低)

conosle

construct (低)

constract

controller (高)

controler

cors (低)

Cross-Origin Resource Sharingの略称

cors

create (中)

creat

creator (高)

creater

css (低)

ccs

要文脈チェック。

datetime (中)

datatime

default (高)

defualt

description (高)

discription

developer (高)

developper

diff (低)

diif

docker (高)

dcoker
doker
docer
dokcer
docekr
dcker

eclipse (中)

eclipce
eclips

edge (低)

egde

elasticsearch (中)

elatic
elaticsearch

enable (低)

enalbe

environment (高)

enviroment

env万歳…

error (中)

erorr

filter (低)

fillter

finally (低)

finaly

form (低)

<from

<form>タグのtypoがたまにあります。

focus (低)

forcus

foundation (低)

fundation

function (高)

fucntion
funciton

getElementById (低)

getElementsById

getElementsByClassName (低)

getElementByClassName

githubpages (低)

githubpage

global (中)

gloval

googleappsscript (高)

googleappscript

gradle (低)

gladle

gulp (中)

glup

hello (高)

hallo
hellow

http (低)

htttp
htpp
htp

ifttt (低)

ifftt
iffft
itfff

imagemagick (高)

imagemagic

参考:ImageMagickってなんでmagicじゃなくてmagickなの??? って思ったから調べた - Qiita

import (低)

inport

information (高)

infomation

integer (低)

intger

intellij (高)

intelij
intellj
intelj

install (高)

isntall
instal

jquery (高)

jqery
jqury
jquey
jquerry
jquer
jqeury

jupyter (中)

jupiter

kotlin (中)

kotolin

kubernetes (低)

kubernates

lambda (高)

lamda
lamba
lamdba
lamdda
lambba

しゃーない。

language (中)

langage

フランス語圏以外ならtypo濃厚です。

laravel (中)

lalavel
laraval

気持ちわかる!が古いめな記事が多い。

linux (低)

linax

license (高)

licence
lisence
lisense

mastodon (低)

mastdon

merge (高)

marge

message (低)

mesage

mozilla (低)

mozila

nginx (低)

enginx

node (低)

ndoe

notifier (低)

notifer

origin (中)

origine
orign

override (低)

overide

parcel (低)

percel

performance (低)

peformance

plugin (低)

plagin

postgresql (高)

postgressql

わかる!略称が「ポスグレ」や「postgres」なのが悪いと思います!

programming (高)

programing

参考:「プログラミング」はprogrammingか、programingか

property (中)

propety

puppeteer (低)

puppeter
pupeter
puppter
pappeter
pupetter

(案外ひっかからない…私だけ?)

python (中)

pyton
pyhton
pyhon

qiita (高)

qita
qiit
quiita (スペイン語のキがquiらしい)
qitta

raspberry (高)

rasberry

ひどい罠だ。

raspbery
rasbery

raspbian (高)

rasbian

require (中)

reqiure
requrie
reuqire

receive (高)

recieve

return (低)

reutrn
retun

scratch (中)

scrach

script (高)

scirpt
scrit

search (高)

seach
serach

separate (中)

seperate

service (高)

serivce

share (低)

shere
(shareのtypoだと思ったらsphereのtypoだったりしました)

sign (高)

sing

slack (低)

slak

standard (中)

standerd

storage (高)

strage

studio (低)

stadio

support (中)

suport

swift (低)

swfit

switch (高)

swith
swich

table (中)

tabel

tampermonkey (低)

tempermonkey

true (低)

treu

ubuntu (中)

ubntu
ubutu
ubunt

unknown (高)

unkown

update (中)

updata

udpate

url (低)

ulr

variable (中)

valiable

verbose (低)

varbose

version (低)

varsion

webhook (低)

webhock

window (高)

widow
windw

(window*s*としての検出が多い)

word (低・注)

ward
区や人名などの意味で使われている場合があるので注意!

yarn (低)

yarm

yield (高)

yeild

zabbix (低)

zabix

未だに (低)

今だに

今まで (低)

今ままで

解決法 (中)

解決方

仮想 (高)

仮装

技術書典 (低)

技術書展
技術書店

群 (中)


文脈要注意!

後述 (低)

後術
(ごじゅつと聞き間違い?)

使用 (低)

仕様する
仕様した
仕様して

対処法 (中)

対処方

多用 (低)

多様する

捕捉 (中)

exceptionを補足
例外を補足

復号 (中)

復号化
複合化
複号化

「暗号化」の反対は「復号化」じゃなくて「復号」なんだよ - Qiita

変換ミスだけ正すか、化を取り除くかは考え中…

プログラム (低)

ブログラム

メソッド (高)

メッソド
メッソッド
メソッッド

メソドは意図的にそういう表記にしているように見受けられました。

用意 (低)

容易する

ライブラリ (中)

ライブライ

リクエスト (低)

リクスト

```

^([^#\n\s]+)(?<!```|<\/h\d>)\n```[^\n`]+\n
改行忘れによるコード挿入の崩れを検出したいのですが、
正規表現がヘタっぴなので、最初の```と最後の閉じる```がうまく区別できません…
ので、最初だけにつけられるシンタックス指定や名前がある場合に反応するようにしてみました。

更新)見出し(#, <h1>)やコード挿入が閉じられすぐさまコード挿入が始まった場合も崩れないので微調整しました。

Markdownのリンク貼り忘れ

[hoge]()
何を入れたかったかは推理するしかないので修正が難しいです。

番外

regist

(残念ながら?)(日本)人口に膾炙しきった気がします。

エンジニア

ITエンジニアをエンジニアと呼ぶなというアレです。
どうでしょうか。
Qiita スラド はてな StackOverflow teratail
は属性的にセーフだと思いたいです。
(ITエンジニアの)個人ブログ程度でもOKと思いたいのはちょっといきすぎでしょうか。

オブジェクト思考

うーん!
ありがちそうなオブジェクト指向の誤変換ですが、
オブジェクト(指向)思考、オブジェクトとして考える、とも読めなくもないです。
プラス思考なども言葉として定着しているので、判定カロリーが高いです。
「日本語としてそれはないです」とご助言貰えたら加えたいですね。

なるほどITエンジニアならではの変換ミスそうです。
文脈が不明ですが、チェックする方も全て文脈チェックが必要で大変そうです。
1

こんにちわ など

日本語警察ではないので言い出したらキリがないですね。
砕けた文体は好むところでもありますので、「適切さ」の指標は技術寄りにした方が双方幸せなのかなと思います。

傾向

隣合わせのキー m,n
指の先走り scirpt, fucntion
ローマ字読み風 brawser
ローマ字読み風で消えるもの comand
同音異義っぽい dataTime
発音 gladle
増える innnodb

iの位置がよくずれている気がします。
末尾忘れ系は正解も引っかかるので注意!

検索予想

Qiitaはたぶん索引型全文検索(形態素解析)だと思うので、英語は単語ごとに分かれると思います。
なのでおそらく、scriptで検索すると'javascript'はひっかからないと思う。
scriptのtypoを探すにはscriptjavascript(coffeescript)両方いろいろで調べなければならない。と思う。

""も完璧に効かないので、文章系なども苦手ですね。

逆に2語が連結し偶然できてしまったり、1単語中にフレーズがでるなどのノイズが部分一致では出てしまいます。
単語単位で賢く区切る形態素解析はよりtypo向けな検索方法かと思います。

高度な検索がしたい場合はGoogleで
site:qiita.comとつけて調べる手もありますね。

文脈に注意!

たとえば、
新人エンジニアの皆さんへ、先輩に相談する前にやるべきことと、上手な相談の方法 - Qiita
では

hello.c
#include <studio.h>

というコードが登場します。
もちろんstdio.hのtypoですが、これは間違いの実演です。

以下の C 言語のプログラムにはバグあります。

hello.c

#include <studio.h>

int main(void)
{
  printf("Hello World\n");
  return 0;
}

gcc でコンパイルしようとすると、以下のようになりました。

$ gcc hello.c
hello.c:1:20: fatal error: studio.h: No such file or directory
compilation terminated

これをみて、先輩に「なんかエラーでたんですけどどうしたら良いですか」なんて聞いてはいけません。

この記事を機械的に調べて編集リクエストを出すと、それは的外れな、記事を悪くする編集リクエストになってしまいます。

typo自体が別の意味の単語になることもありますし、人名やひねったサービス・ソフト名の場合もあるので、しっかり前後の文章を読みましょう。

(この記事自体もtypoの宝庫ですし)

引用元のミスに注意!

typoが投稿者以外の責の場合も、スルーが宜しかろうと思います。
特にURLの場合は機能しなくなりますからね。


https://forum.sublimetext.com/t/sublime-text-for-raspberry-pi-3-with-rasbian-os/21066

(rasbian → raspbian)

typoとスペルミスについて

typoは「正解を知っているが、打ち損なった(誤変換した)」
スペルミスは「スペルを誤って覚えていた」
という割合が多いかもor分類ができるかもしれません。

typoの修正はlint的気分でできますが、
スペルミスの指摘は教育と受け取られるかもしれません。
…それは鬱陶しい。かも。
複数形にならない単語の修正(datas → data)などは、
お勉強・無知を指摘するような編集リクエストになりかねず、(編集者にとって)パワーが強すぎる。かも。

個人的にそもそも複数形などは「data(という変数名で渡された)を収めた配列」などで使いそうですし、わかりやすい面もあるので…2

気まずくならず、その後、一緒に冒険に出かけ、友人になれる可能性もありますし、難しい問題かもですね。
私は英語がさっぱりなので指摘しようがないのですが。

ユーザースクリプト

普段からtypoを意識して文章を読むことはありません。
ましてタイポグリセミアがあるのでなかなか気づくことが難しい。
かといってtypoを探すために記事を漁ることは、上のリストを使っても暇でもない限り効率が悪いです。
そこでユーザースクリプトを使って、普段遣いの中でtypoがあれば目に付きやすくしてみます。

スクリーンショット

before
image.png

after
image.png

typoがあることを視覚的に強調します。(雑)

動作確認環境

  • Firefox 59
  • Tampermonkey v4.6.5709

コード

ブラウザのJavaScriptでは正規表現の先読みはできても後読み(lookbehind)ができないと知ってびっくり!
Node.jsでは動いたのですが…

ES2018で追加される機能まとめ - Qiita #後読みの追加

ES2018で実装。6月頃リリースらしいので、ブラウザの実装はその前後なんでしょうね。

と思いきや、Chromeはやはり早くて、
RegExp lookbehind assertions - Chrome Platform Status

Status in Chromium

Blink components: Blink>JavaScript>Language

Enabled by default (tracking bug) in:

Chrome for desktop release 62
Chrome for Android release 62
Android WebView release 62
Opera release 49
Opera for Android release 49

Consensus & Standardization

Firefox:
No public signals
Edge:
Public support
Safari:
No public signals
Web Developers:
Strongly positive

62で実装済み!Edgeも!
とFirefoxが後塵を拝している状態ですね…。

スクリプトでは一旦Firefox用に置換で否定後読みを消しておきます。

typoを強調するuser.js(クリックで開く)
// ==UserScript==
// @name         Qiita typo checker
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  自作辞書で本文中のスペルミスを強調する
// @author       khsk
// @include      https://qiita.com/*/items/*
// @include      https://qiita.com/*/private/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const url = new URL(location.href);
    // 編集履歴(items/乱数/revision)などを開いたときに動かさないようにする
    if (!/\/[^/]+\/(items|private)\/[^/]+\/?$/.test(url.pathname)) {
        return;
    }

    // GitHubのdictionary.jsをコピペする
    const DICTIONARY = {
        access                 : ['(?<!e)acces(?!s)', 'acess', 'aceess',],
        action                 : ['aciton', 'actoin',],
        android                : ['andriod','andoroid',],
        analyze                : ['analize',],
        arduino                : ['ardino','aruduino','arudino',],
        behavior               : ['(?<!mono)behaviour',],
        browser                : ['brawser',],
        bootstrap              : ['boostrap','bootsrap','bootstap',],
        bottom                 : ['botton','buttom',],
        button                 : ['botton','buttom',],
        bulma                  : ['bluma',],
        calendar               : ['calender',],
        callback               : ['calback','collback',],
        chainer                : ['chiner',],
        channel                : ['chanel','channnel',],
        chocolatey             : ['chocolatery',],
        chrome                 : ['chorome',],
        chromium               : ['chronium',],
        class                  : ['calss',],
        coffee                 : ['cofee', 'coffe(?!e)',],
        command                : ['comand',],
        comment                : ['coment','commnet',],
        commit                 : ['comit',],
        communication          : ['comunication',],
        community              : ['comunity',],
        composer               : ['compoesr','compsoer','conposer'],
        configure              : ['configre',],
        console                : ['conosle',],
        construct              : ['constract',],
        controller             : ['controler',],
        cors                   : ['(?<!mi)cros(?!s)',],
        create                 : ['(?<!o_)creat(?!e|ion|ive|ing|or)',],
        creator                : ['creater(?!ole|epo)',],
        css                    : ['(?<!a)ccs',],
        datetime               : ['datatime',],
        default                : ['defualt',],
        description            : ['discription',],
        developer              : ['developper',],
        diff                   : ['diif',],
        docker                 : ['dcoker','doker','docer','dokcer','docekr','dcker',],
        eclipse                : ['eclipce','eclips(?!e)'],
        edge                   : ['egde',],
        elasticsearch          : ['elatic',],
        enable                 : ['enalbe',],
        environment            : ['enviroment',],
        error                  : ['erorr',],
        filter                 : ['fillter',],
        finally                : ['finaly',],
        focus                  : ['forcus',],
        form                   : ['<from',],
        foundation             : ['fundation',],
        function               : ['fucntion', 'funciton',],
        getElementById         : ['getElementsById',],
        getElementsByClassName : ['getElementByClassName',],
        githubpages            : ['github(\\s|-|_)?page(?!s)'],
        global                 : ['gloval',],
        googleappsscript       : ['google\\s?app\\s?script',],
        gradle                 : ['gladle',],
        gulp                   : ['glup',],
        hello                  : ['(?<!s)hallo','hellow(?!orld)',],
        http                   : ['htttp','htpp','htp(?!asswd)',],
        ifttt                  : ['ifftt','iffft','itfff',],
        imagemagick            : ['imagemagic(?!k)',],
        import                 : ['inport',],
        information            : ['infomation',],
        integer                : ['intger',],
        intellij               : ['intelij','intellj','intelj',],
        install                : ['isntall','instal(?!l)',],
        jquery                 : ['jqery','jqury','jquey','jquerry','jquer(?!y)','jqeury',],
        jupyter                : ['(?<!junit.)jupiter',],
        kotlin                 : ['kotolin',],
        kubernetes             : ['kubernates',],
        language               : ['langage',],
        lambda                 : ['lamda', 'lamba', 'lamdba','lambba','lamdda',],
        laravel                : ['lalavel','laraval',],
        linux                  : ['linax',],
        license                : ['licence','lisence','(?<!intel)lisense',],
        mastodon               : ['mastdon',],
        merge                  : ['marge'],
        message                : ['mesage',],
        mozilla                : ['mozila',],
        nginx                  : ['enginx',],
        node                   : ['(?<!joh)ndoe',],
        notifier               : ['notifer',],
        origin                 : ['origine','orign',],
        override               : ['overide',],
        parcel                 : ['percel',],
        performance            : ['peformance',],
        plugin                 : ['plagin',],
        postgresql             : ['postgressql',],
        programming            : ['programing',],
        property               : ['propety',],
        puppeteer              : ['puppeter','pupeter','puppter','pappeter','pupetter',],
        python                 : ['pyton','pyhton','pyhon',],
        qiita                  : ['qita','qiit(?!a)','quiita','qitta',],
        raspberry              : ['rasberry','raspbery','rasbery',],
        raspbian               : ['rasbian',],
        require                : ['reqiure','requrie','reuqire',],
        receive                : ['recieve',],
        return                 : ['reutrn','retun',],
        scratch                : ['scrach',],
        script                 : ['scirpt','scrit',],
        search                 : ['seach','serach',],
        separate               : ['seperate',],
        service                : ['serivce',],
        share                  : ['shere',],
        sign                   : ['(?<!u|mis)sing(_|\\s)?(out|in|up|off)',],
        slack                  : ['slak',],
        standard               : ['standerd',],
        storage                : ['strage',],
        studio                 : ['stadio',],
        support                : ['suport',],
        swift                  : ['swfit',],
        switch                 : ['(?<!start|end)swith','swich',],
        table                  : ['tabel',],
        tampermonkey           : ['tempermonkey',],
        true                   : ['treu',],
        ubuntu                 : ['ubntu','ubutu',],
        unknown                : ['unkown',],
        update                 : ['updata(?!ble)','udpate',],
        url                    : ['ulr',],
        variable               : ['valiable',],
        verbose                : ['varbose',],
        version                : ['varsion',],
        webhook                : ['webhock',],
        window                 : ['widow','windw',],
/*        word                   : ['(?<!for)ward',], // 一般的すぎるので除去 reward award ... */
        yarn                   : ['yarm',],
        yield                  : ['yeild',],
        zabbix                 : ['zabix',],
        未だに                 : ['今だに',],
        今まで                 : ['今ままで',],
        解決法                 : ['解決方(?!法)',],
        仮想                   : ['仮装',],
        技術書典               : ['技術書展','技術書店',],
                             : ['',],
        後述                   : ['後術',],
        使用                   : ['仕様(され|する|した|して|しま|が?でき)',],
        対処法                 : ['対処方(?!法)',],
        多用                   : ['多様(され|する|した|して|しま|が?でき)',],
        捕捉                   : ['exceptionを補足','例外を補足',],
        復号                   : ['復号化','複合化','複号化',],
        プログラム             : ['ブログラ(ム|ミ)',],
        メソッド               : ['メッソド','メッソッド','メソッッド',],
        用意                   : ['容易(され|する|した|して|しま|が?でき)',],
        ライブラリ             : ['ライブライ',],
        リクエスト             : ['リクスト',],
        を比較して             : ['の比較して',],
        '\\n```'               : ['^([^#\\n\\s]+)(?<!```|<\\/h\\d>)\\n```[^\\n`]+\\n',], // 正規表現がヘタなので簡易版
        URL抜け                : ['\\]\\(\\)',], // 検出してもどのURLを入れればいいのか難しい
    };


    const replaceFunction = (correct, match,offset, string) => {
        return '【TYPO: ' + correct + '' + match + '【/TYPO】';
    };

    // http://cwestblog.com/2014/03/14/javascript-getting-all-text-nodes/
    const getTextNodesIn = (elem, opt_fnFilter) => {
        var textNodes = [];
        if (elem) {
            for (var nodes = elem.childNodes, i = nodes.length; i--;) {
                var node = nodes[i], nodeType = node.nodeType;
                if (nodeType == 3) {
                    if (!opt_fnFilter || opt_fnFilter(node, elem)) {
                        textNodes.push(node);
                    }
                }
                else if (nodeType == 1 || nodeType == 9 || nodeType == 11) {
                    textNodes = textNodes.concat(getTextNodesIn(node, opt_fnFilter));
                }
            }
        }
        return textNodes;
    };

    const textNodeFilter = node => {
        return node.textContent !== '';
    };

    const textNodes = getTextNodesIn(document.querySelector('html'), textNodeFilter);
    textNodes.forEach(textNode => {
        Object.keys(DICTIONARY).forEach(correct => {
            // Firefoxのみ/(否定)?後読み/実装がまだなので、置換で消しておく
            //console.log('(' + DICTIONARY[correct].join('|').replace(/\(\?<!.+?\)/, '') + ')'); // debug
            let regexp = new RegExp('(' + DICTIONARY[correct].join('|').replace(/\(\?<!.+?\)/, '') + ')', 'gim');
            textNode.textContent = textNode.textContent.replace(regexp, replaceFunction.bind(null,correct));
        });
    });

})();

JavaScript - javascriptの変数のメモリへの割当について(72635)|teratail

かなり大きい変数を持つのでメモリが気になりますが、即時関数は実行後に開放されるものでしょうか。

計算量はかなり富豪的なかけ算ですが、outputがなければ実行速度は気になりませんでした。
最近のブラウザやCPUはすごいですね。

部分一致による定期全件チェックスクリプト

typoを意識的に手動で探すことは効率が悪い。
かといって閲覧したページのtypoを見つけやすくしただけではチェック数が少なすぎる。
ここまで来たら自動化だ!
ということで、Qiita APIで記事を取得してtypoチェック。
typoがあればSlackへ通知するスクリプトを書きました。

khsk/Qiita-typo-checker: Qiitaの投稿からtypoを検出します。

スクリーンショット

image.png

運用結果

半月ほど使ってみました。

楽!

まあ当然ですね。
全件検索しているので、上のQiita内での検索は既存typoではしなくなりましたし、
ユーザースクリプトによる強調も、トレンドやタグフィード(≒新しい=自動チェック済み)では意識しなくなりました。
でも消すのは悲しいのでこうやって残しておきます。

検出数

大体投稿数が一日300前後ほどで、そのうちのtypoが誤検出含めて10件行かない程度です。

多い誤検出

  • ~sWith~
  • createR~

この2つが毎回1,2件を占めるのでスルーして、
プラスccs,behaviourがその次にそこそこでるので、
実質5件送れば多いほうでしょうか。
朝確認して10,20分もかからない作業負荷でした。

気まずい

例えば
~~してみる その1
のコードのtypo(コメントやprint文字列などエラーにならない類)を修正して採用されるものの、
手元のコードを修正するわけではないので数日後の
~~してみる その2
ではまた同じ箇所がtypoしている。

また同じ編集リクエストを出すのはひじょ~に気が引け、勝手に変な雰囲気を感じます。
その時は「自分はtypoを検出して自動で編集リクエストを出すbotだ」と言い聞かせて出します。

…3度めのときは気分によって出さないときもありました。

Qiita検索のここがイマイチ

作成日時順にソートができるが、更新順にはソートできない点。
どうせ編集リクエストを送るなら、採用されやすい、今でも保守されている記事=更新日時が新しい記事に優先して送りたい。
しかし作成日時でしかソートできないので、古いけど最近更新した記事への編集リクエストが漏れる。

流石に数年前が最終更新日時の記事に編集リクエストを送るのは気が引けるので…。

Qiitaレイアウトのここがイマイチ

既存の編集リクエスト数が記事からわからない。
旧レイアウトでは記事画面で編集リクエスト数が見えるので、

image.png

編集リクエストについて - Qiita:Support

閲覧者も誤記やよりよい表現の可能性があるとすぐに気づけて、編集リクエストを確認できました。
今は編集リクエスト一覧に行かなければある無しも確認できず、2アクション必要で非常に手間です。

編集リクエストを確認しないと、こんなことになります。

image.png

"経営者マインドが足りない!vs. 現場に任せてくれない!の対立をなくすカードゲームをつくった話"への編集リクエスト一覧 - Qiita

一人イキった編集リクエストを出している人がいやがりますが
実は同じ内容の編集リクエストが複数出されています。
自分なら間違いの指摘は嬉しいですが、同じ指摘を何度もされるとメンタルがフルボッコになるので…
既存の編集リクエストの確認は怠れません…。3

Qiita編集リクエストのここがイマイチ

リジェクトされた編集リクエストを確認できない。
リジェクト時に編集リクエストを出した人に通知もされないので、自分のように巡回して出していると、
「もしかして過去に出してリジェクト済みかな?」
と思うときがあります。
投稿者からしたら、
前にリジェクトした編集リクエストがまた来やがった!
と煽ってる感がありますし、何より双方にとっても負担。
さすがにtypo修正程度をリジェクトする人はあまり居ないと思う(無視する方が楽なので)のですが、
つい最近、自分がシンタックスハイライトの編集リクエストをリジェクトしたので、他人事ではないということをひしひしと感じました。

Qiita編集リクエストのここがイマイチ2

編集リクエストが受け入れられた記事へのコメントが通知される。
typo修正は記事自体には興味がないこともあるので、結構無関係な通知です。
(コメント読むことは好きなのですが)
ここは通知設定で選べればいいと思いました。

Qiita編集リクエストのここが…良いかも

編集リクエストに対するいいねやコメントが出来ないという声はたまに見かけます。

  • いいねしたい
  • 本文・コメントに謝辞を載せる
  • twitterで直接お礼を述べる

などなど。
自分もGitHubみたいにあれこれコメントやブラッシュアップ議論が出来てもいいかなあと思っていました。
ただ、今回バッチを使い無心で送っていると、「送った→採用された」で完結する関係はコミュニケーションコストが低くていいなと少し思いました。
プルリクエストみたいにどんなコメントが来るか不安になることも無いし、儀礼的いいねをつけ合う面倒臭さが生じる可能性すらないので、案外送るハードルを低くしているのかなと思いました。


(2018/07/06追記)
Qiitaのスタンスらしきものを記録


編集リクエストの心得

いろいろモヤモヤも書きましたが、無心に、機械的に直して送信し、忘れてしまうことが一番だと思います。
最悪、既存の編集リクエストも確認しないでいいかも。(「重複する→投稿者が確認しない人」の人が多そう)
いわゆる「仕事のボール」と同じですね。投げる方が強い!
投げたボールがどうなるかは考えないほうがいいですね。
そもそも採用率は100%ではなく、続けていくとどんどん送信済みリクエストが溜まっていきます。
「まだ確認されてないかな?」ともやもやするよりは忘れた方が精神的によいです。
採用されたらサプライズだと思って喜んでおきましょう。
それが低メンタルでの防衛術です。

4

編集リクエストの副作用

フォロワーがたまに増えます。
…いいんですが、typo修正以上にためになる活動はしてないと思います。(タイムラインにも出ないし)

車輪の再発明に気をつける

今回は記事を書く過程で
「typoを暇なときに探している→探し方紹介&リスト化」
から
「アクティブな活動(検索)からパッシブな活動としてのユーザースクリプトの作成」
さらに
「typoを探すというルーティンワークの自動化」
と目的が遷移していきました。
最終的には文章全体のスペルチェックですね。
流れを汲んで、チェックするリストは最初に作ったものを追記・修正して使っています。

でも、世の中にはspell checkerなんてたくさんあります
日本人のプログラマ文脈特化としてもあの自作リストは優秀ではないでしょう。

最初のちょっとしたツールを作る~自分がミスした単語を登録するレベル~段階では良いのですが、変に大げさになってくると、
「自作するより既存のものを組み合わせて使うほうが優秀じゃないか?」
という、ほぼ正しいであろう疑念がつきまといます。リストの更新も大変ですしね!

車輪の再発明は完全な悪ではありません。
自分で自分が欲しいものを作っていて楽しくないわけでもありません。

でも何が大切かを見失わず、今回で言えば、
もし「本気でtypoをよりよく見つけたい」なら
どこかのタイミングで既存のspell checkerをチェックしてみるべきでしたでしょう。

このあたりはサンクコストとも絡んできそうです。
趣味なら無問題ですが、プロダクトのときなどは気をつけたいですね。

参考

typo修正大事!

編集リクエスト使おう!

ユーザーが編集リクエストを送った後に増えた記事nの「いいね!」数

編集リクエストにいいねとは…。(「後」抜けかな)
編集リクエスト採用された後(たぶん「送った後」じゃないと思うんですけど)のいいねは100までひとつ0.1Contributionとして加算されます。
体感、小数点計算されてます。
(いち記事で10いいね増えなくても複数記事合計10いいねで1Contributionもらえてそうです)

慣れたらGitHubにもプルリク送っちゃいなよ

間違いやすい文字列

こういったものを使えば更に作業を自動化かつ網羅性も高められて、楽かもしれない。

表記ゆれ

  • Qiitaの◯◯.jsタグの表記ゆれと最も使われているタグ一覧 - Qiita
    typo修正の動機の一つに、「タグ補完で間違ったタグ選択してしまう」問題があります。
    これは一人でも編集リクエストを採用しなければ解決しないので諦めましたが、タグが間違っているせいで人目に触れない記事というのも過去見てきましたので余力があれば寄せてあげたいですね。
    せめて「候補に出すのは10個以上投稿されたタグ」など閾値があればtypoなタグは候補に出にくくなると思うのですが…。

人様の文章に一様に適用はできないでしょうが、textlintも合わせて使うと、ますます校閲者っぽくなれるかもしれませんね。重版出来!
textlintの公式サイト(オンラインデモ)を作りました | Web Scratch

その他自分の

ToDo 標的型編集リクエスト攻撃

より編集リクエストへのハードルを下げるために、ユーザープロフィールに「編集リクエスト」を含んでいるユーザーを晒し上げるスクリプトを思いつきました。(編集リクエスト 歓迎 となってる可能性が高そうなので)
こちらもリスト化して、検索queryにuser:khskを付け足せば!
編集リクエストを喜んでくれるであろう人だけに狙い撃ってtypo報告ができて気が楽なんじゃないかなあと思いました。

思うだけでは説得力がない!
その実践として、思いついた経緯の
@ledsun

編集リクエスト、コメント大歓迎です。

へ、投稿時期を考慮せずすべてのtypoに編集リクエスト爆撃を敢行いたしました。
結果、送った編集リクエスト8件中8件を処理していただきました。
心より感謝申し上げます。

なお、ビクビクと反応を伺っていると

ということで一安心です。5

てか、Qiitaは投稿された記事の本文をスペルチェックして、編集リクエストを作るbotを作ってくれ

それな。

(@ledsun といえば最強のIT系かあちゃんからたかしへのアドバイス - Togetter が大好きで、優しそうなJ( 'ー`)しな方だったので標的となりました。)

なお、
Qiitaのあれこれをひたすら分析してランキング - Qiita

ということで、現在Qiitaユーザは登録数ベースで 20万 を超えているようです...!

いちページ100件取得なので、取得は2000回。
認証付きAPIは1時間に1000回なので、毎回4秒スリープを挟めばいいですね。
JSは自作しないといけないのがつらみですが…
JavaScript(Node.js) で sleep() アラカルト - Qiita

ToDo 修正箇所減

user.jsとnode.jsの辞書を統合しないと、修正箇所がこの記事含めて3つになってしまう…

ToDo 編集リクエスト自動化

文脈や誤検出を無視してしまうならば、puppeteerを使って自動で編集リクエストを出してしまうことも可能なんじゃないかなあと思っています。
個人でやるのはちょっと横暴すぎる気がするのでたぶんしませんが、運営やネームバリューあるとこがしてくれたらいいですね。

オマケ

Qiita検索への置換正規表現

^([^# \n]+)$

[\1](https://qiita.com/search?utf8=%E2%9C%93&sort=created&q=\1)

オマケ雑談

編集リクエストを送っていて嬉しい瞬間。それはもちろん修正された記事にいいねがつくことです。
特に良さそうな記事なのにメジャータグをtypoしてるせいで埋もれてそうな記事がタグ修正後にいいねがもらえると良いですね。

というのも、(おそらくまだ)今のQiitaは、記事のタグを修正するとそのタグの新着としてフィードに再浮上します。
うっかりtypoへのセカンドチャンスとして喜ばしい仕様です。
が、悪用も見かけてきまして、最近では

エンジニアで稼ぐために大切な20のコト - Qiita #コメント

Qiitaに問い合わせをした所、タグを付け替えることで新着記事としてトップに表示される仕様は近々廃止予定だそうです。

だそうです。悲しいですね。
QiitaでFont Awesome諸々が死んだ ユーザーインプットフィルターの導入 - Qiita
と合わせて悪用により好きだった機能が死んでいくのは残念です。

注意

この記事は更新時、ストックしている人に通知する可能性があります。


  1. 方な文章でも狙って型で書ゐてそうなので、一流の冗句なんでせうか。 

  2. コールバックの引数のサンプルがdataでそれを集める処理を書くときにdatasにpushするとか…。サンプルの引数名って使うときにあまり変えません。 

  3. と書きましたが、記事書きながら現在進行系で何個も編集リクエスト出していると、流石に億劫になってきました…。「編集リクエストの心得」は他の人の参入障壁を下げる目的で、自分は確認するぞー!と思っていたのですが…。

    画像の現象はQiitaのポエムの王者記事の良質さと編集リクエストを処理しないスタンスがたまたま合致したレアケースだと思います。 

  4. 編集リクエストは投稿者にとって負担だという記事もありますが、あまり触れずにおきます。 

  5. Qiitaで編集リクエスト歓迎明言してくれる人って、神ですよ。 

90
55
13

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
90
55