.NET Framework に設計を学ぶ : メソッド名の頻出接頭辞

名前大事。間違いなく大事。名前大事を逆手にとって、名前をランダムに改変して読めなくするツールがあることからも、名前がいかに大事かわかります。
名前大事はわかるけど、うまい名前が浮かばないことがよくあって、そういうときはメソッドがうまく設計できていないときだったりもします。じゃあ、きれいに設計されている .NET Framework を調べて、名前付け/設計の極意を学んでみようと思いついたのでやってみました。
あまり手を広げると大変なので、今日はメソッド名の頻出接頭辞を調べてみました。

MSDN にあるメソッド名のガイドラインから抜粋

  • .NET ではメソッド名は ToString のように Pascal 形式で名前を付けます
  • パラメータ名などには typeName のように Camel 形式を使います
  • メソッド名には動詞または動詞句(うーん、苦手…)を使います
  • 通常、メソッドはデータを操作するため、メソッドのアクションを表す動詞を使用すると、開発者にはメソッドの機能がよりわかりやすくなります
  • メソッド名には実装の詳細を使用しないでください
    • how をメソッド名にするな、what をメソッド名にしろってことですね

RemoveAll, GetCharArray, Invoke などが正しい例として挙げられています。

メソッド名のパターン

例えば、.NET では Int32 にも Byte にも TryParse という似たようなメソッドがあって経験が活かせるように配慮してあります。同じように自作のクラスにも文字列を解析して、成功したらオブジェクトを返すメソッドを作るなら TryParse の名前を付けて、引数の形式もまねれば、.NET の経験がある人なら迷わずに使えます。
ちなみにこれは TryParse パターンとして MSDN に載ってます。例外を返す Parse と例外を返さない TryParse という2つのメソッドを用意して、例外によるパフォーマンス低下を回避する手段だそうです。クラス作成者には例外ありなしのどっちがいいかわからないことがよくあるので、両方用意するならこのパターンを使えってことですね。

また、文書化されてるかわかりませんが、ApplySort と ApplySortCore のように、一つのメソッドを何らかの理由で二つにわけないといけない場合、接尾辞として Core を付けるというパターンもあります。

その他。

  • イベントを発生させるメソッドの接頭辞 OnEventName
  • 非同期パターン接頭辞 Beginなんとか、Endなんとか
  • 特殊なメソッド名(?) Dispose, Close
    • どこからどこを特殊とするか迷いますが、これらは C# では using による言語サポートがあり、通常のメソッド名とはなんとなく区別したい

接頭辞 Top30

どこまで厳密に分析するか考えるだけで何日もかかりそうなので、テキトーな感じでやりました。調べるのに使ったソースコードは最後に載せます。対象アセンブリもコードを見てください。
頻出順に「出現回数 : 割合 : 累積割合 : 接頭辞」。一段下げてその接頭辞を持つメソッド名の頻出順に「出現回数 : 割合 : メソッド名」としました(2009/8/18 追記: 割合と累積の割合を追加しました)。
やはり Get は圧倒的です。Render は意外ですが、そのほかは役に立ちそうです。

* Methods (29,496)

 5125 : 17.38% : 17.38% : Get
	  392 : 1.33% : GetHashCode
	  269 : 0.91% : GetEnumerator
	  114 : 0.39% : GetObjectData
	   89 : 0.30% : GetAsFrozenCore
	   88 : 0.30% : GetCurrentValueAsFrozenCore

 2799 : 9.49% : 26.86% : On
	   82 : 0.28% : OnPreRender
	   65 : 0.22% : OnCreateAutomationPeer
	   63 : 0.21% : OnInit
	   44 : 0.15% : OnKeyDown
	   34 : 0.12% : OnChanged

 1295 : 4.39% : 31.26% : Create
	  236 : 0.80% : CreateInstanceCore
	   71 : 0.24% : Create
	   52 : 0.18% : CreateNewElement
	   39 : 0.13% : CreatePermission
	   37 : 0.13% : CreateAccessibilityInstance

 1058 : 3.59% : 34.84% : Add
	  408 : 1.38% : Add
	   73 : 0.25% : AddRange
	   36 : 0.12% : AddChild
	   35 : 0.12% : AddAttributesToRender
	   34 : 0.12% : AddText

  949 : 3.22% : 38.06% : Set
	   28 : 0.09% : SetItem
	   24 : 0.08% : SetValue
	   18 : 0.06% : SetBoundsCore
	   17 : 0.06% : SetParent
	   16 : 0.05% : Set

  895 : 3.03% : 41.09% : Remove
	  358 : 1.21% : Remove
	  203 : 0.69% : RemoveAt
	   22 : 0.07% : RemoveItem
	   17 : 0.06% : RemoveAll
	   13 : 0.04% : RemoveRange

  850 : 2.88% : 43.98% : To
	  474 : 1.61% : ToString
	   50 : 0.17% : ToXml
	   18 : 0.06% : ToDateTime
	   12 : 0.04% : ToFourDigitYear
	   12 : 0.04% : ToSqlString

  735 : 2.49% : 46.47% : Clone
	  370 : 1.25% : Clone
	  153 : 0.52% : CloneCurrentValue
	   99 : 0.34% : CloneCore
	   88 : 0.30% : CloneCurrentValueCore
	   17 : 0.06% : CloneNode

  700 : 2.37% : 48.84% : Begin
	  506 : 1.72% : BeginInvoke
	   26 : 0.09% : BeginInit
	   10 : 0.03% : BeginEdit
	   10 : 0.03% : BeginRead
	   10 : 0.03% : BeginWrite

  694 : 2.35% : 51.19% : Is
	   47 : 0.16% : IsDefaultAttribute
	   34 : 0.12% : IsSubsetOf
	   25 : 0.08% : IsDefined
	   25 : 0.08% : IsUnrestricted
	   15 : 0.05% : IsInputKey

  686 : 2.33% : 53.52% : End
	  505 : 1.71% : EndInvoke
	   27 : 0.09% : EndInit
	   10 : 0.03% : EndEdit
	   10 : 0.03% : EndRead
	   10 : 0.03% : EndWrite

  615 : 2.09% : 55.60% : Invoke
	  550 : 1.86% : Invoke
	   41 : 0.14% : InvokeEventHandler
	    8 : 0.03% : InvokeMember
	    3 : 0.01% : InvokeAsync
	    2 : 0.01% : InvokeScript

  509 : 1.73% : 57.33% : Copy
	  330 : 1.12% : CopyTo
	   96 : 0.33% : Copy
	   53 : 0.18% : CopyFrom
	   15 : 0.05% : CopyProperties
	    2 : 0.01% : CopyToRows

  392 : 1.33% : 58.66% : Equals
	  391 : 1.33% : Equals
	    1 : 0.00% : EqualsExact

  383 : 1.30% : 59.96% : Clear
	  300 : 1.02% : Clear
	   21 : 0.07% : ClearItems
	    3 : 0.01% : ClearContainerForItemOverride
	    3 : 0.01% : ClearCore
	    3 : 0.01% : ClearSelection

  381 : 1.29% : 61.25% : Can
	  110 : 0.37% : CanConvertFrom
	  107 : 0.36% : CanConvertTo
	   32 : 0.11% : CanConvertFromString
	   32 : 0.11% : CanConvertToString
	   19 : 0.06% : CanBuildChannelListener

  372 : 1.26% : 62.51% : Convert
	  132 : 0.45% : ConvertTo
	  124 : 0.42% : ConvertFrom
	   36 : 0.12% : ConvertFromString
	   33 : 0.11% : ConvertToString
	   17 : 0.06% : Convert

  371 : 1.26% : 63.77% : Write
	   50 : 0.17% : Write
	   29 : 0.10% : WriteTo
	   19 : 0.06% : WriteLine
	   16 : 0.05% : WriteContentTo
	   10 : 0.03% : WriteByte

  360 : 1.22% : 64.99% : Contains
	  289 : 0.98% : Contains
	   40 : 0.14% : ContainsKey
	    6 : 0.02% : ContainsValue
	    4 : 0.01% : ContainsAudio
	    4 : 0.01% : ContainsFileDropList

  340 : 1.15% : 66.14% : Render
	  128 : 0.43% : Render
	   26 : 0.09% : RenderContents
	   17 : 0.06% : RenderAttributes
	   13 : 0.04% : RenderBeginTag
	   13 : 0.04% : RenderEndTag

  271 : 0.92% : 67.06% : Dispose
	  268 : 0.91% : Dispose
	    1 : 0.00% : DisposeCore
	    1 : 0.00% : DisposeLocalCopyOfClientHandle
	    1 : 0.00% : DisposeObject

  271 : 0.92% : 67.98% : Reset
	  135 : 0.46% : Reset
	   10 : 0.03% : ResetModified
	    9 : 0.03% : ResetAccessRule
	    8 : 0.03% : ResetForeColor
	    7 : 0.02% : ResetBackColor

  269 : 0.91% : 68.89% : Index
	  243 : 0.82% : IndexOf
	   16 : 0.05% : IndexOfKey
	    2 : 0.01% : IndexOfValue
	    1 : 0.00% : IndexFromContainer
	    1 : 0.00% : IndexFromGeneratorPosition

  256 : 0.87% : 69.76% : Read
	   49 : 0.17% : Read
	   11 : 0.04% : ReadByte
	    7 : 0.02% : ReadOnly
	    7 : 0.02% : ReadString
	    7 : 0.02% : ReadXml

  249 : 0.84% : 70.60% : Insert
	  190 : 0.64% : Insert
	   30 : 0.10% : InsertItem
	    5 : 0.02% : InsertAfter
	    5 : 0.02% : InsertBefore
	    3 : 0.01% : InsertAt

  221 : 0.75% : 71.35% : Initialize
	   84 : 0.28% : Initialize
	   38 : 0.13% : InitializeFrom
	   17 : 0.06% : InitializeLifetimeService
	   13 : 0.04% : InitializeCell
	    9 : 0.03% : InitializeDefault

  198 : 0.67% : 72.02% : Load
	   66 : 0.22% : LoadViewState
	   31 : 0.11% : LoadPostData
	   28 : 0.09% : Load
	   17 : 0.06% : LoadControlState
	    8 : 0.03% : LoadAdapterState

  158 : 0.54% : 72.56% : From
	   47 : 0.16% : FromXml
	    6 : 0.02% : FromXmlString
	    5 : 0.02% : FromHandle
	    3 : 0.01% : FromElement
	    3 : 0.01% : FromString

  156 : 0.53% : 73.09% : Find
	   32 : 0.11% : Find
	   13 : 0.04% : FindAll
	    9 : 0.03% : FindName
	    7 : 0.02% : FindControl
	    6 : 0.02% : FindUsersByEmail

  151 : 0.51% : 73.60% : Save
	   61 : 0.21% : SaveViewState
	   28 : 0.09% : Save
	   17 : 0.06% : SaveControlState
	    8 : 0.03% : SaveAdapterState
	    4 : 0.01% : SaveAs

ソースコード

自由にいじって独自の分析をし、ネットで発表してもらえるとうれしいです。
.NET フレームワークをこんなふうに分析して問題ないの?って点は、おそらく問題ないです。メソッド名は MSDN ライブラリで公開されている情報です。また、リフレクションがリバースエンジニアリングとも思えません。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

public static class Reflector
{
  public static void Main()
  {
    string[] asmNames = {
      "mscorlib",
      "System",
      "System.Core",
      "System.Data",
      "System.Data.DataSetExtensions",
      "System.Deployment",
      "System.Drawing",
      "System.Windows.Forms",
      "System.Xml",
      "System.Xml.Linq",
      "PresentationCore",
      "PresentationFramework",
      "WindowsBase",
      "System.Configuration",
      "System.EnterpriseServices",
      "System.Web",
      "System.Web.Extensions",
      "System.Web.Mobile",
      "System.Web.Services",
      "System.Runtime.Serialization",
      "System.ServiceModel",
    };

    var methodNames = from asm in asmNames
      from type in Assembly.LoadWithPartialName( asm ).GetExportedTypes()
      where !type.IsSubclassOf( typeof( Enum ) )
      from method in
        // このあたりをいじるといろいろ分析できる
        type.GetMethods( BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic )
        .Where( m => !m.IsSpecialName ) // プロパティ、イベントハンドラ、演算子を取り除く
        .Where( m => !m.IsAssembly && !m.IsPrivate ) // internal, private を取り除く
        .Select( m => m.Name )
        .Distinct()
      select method.GetMethodName();

    // メソッド総数
    int methodsCount = methodNames.Count();

    // 整列
    var frequentlyMethodNames = methodNames
      .Select( m => new { FullName = m, First = m.SplitCamelWord().ElementAt( 0 ) } )
      .GroupBy( x => x.First, x => x, ( first, xs ) => new
      {
        First = first,
        Count = xs.Count(),
        Elements = xs
          .GroupBy( y => y.FullName, y => y.FullName, ( f, fs ) => new { FullName = f, Count = fs.Count() } )
          .OrderByDescending( b => b.Count )
          .ThenBy( b => b.FullName )
          .Take( 5 )
      } )
      .OrderByDescending( e => e.Count )
      .ThenBy( e => e.First )
      .Take( 30 );

    using ( var sw = new StreamWriter( "name30.txt" ) )
    {
      sw.WriteLine( "* Assemblies ({0:n0})\n", asmNames.Length );
      foreach ( var item in asmNames )
      {
        sw.WriteLine( item );
      }
      sw.WriteLine( "\n" );

      int sum = 0;

      sw.WriteLine( "* Methods ({0:n0})\n", methodsCount );
      foreach ( var item in frequentlyMethodNames )
      {
        sum += item.Count;
        sw.WriteLine( "{0,5} : {2:p2} : {3:p2} : {1}",
          item.Count,
          item.First,
          ( double ) item.Count / methodsCount,
          ( double ) sum / methodsCount );

        foreach ( var m in item.Elements )
        {
          sw.WriteLine( "\t{0,5} : {2:p2} : {1}",
            m.Count,
            m.FullName,
            ( double ) m.Count / methodsCount );
        }
        sw.WriteLine();
      }
    }
  }

  public static IEnumerable<string> SplitCamelWord( this string str )
  {
    if ( str.StartsWith( "get_" ) || str.StartsWith( "set_" ) || str.StartsWith( "add_" ) )
      str = str.Substring( 4 );

    if ( str.StartsWith( "remove_" ) )
      str = str.Substring( 7 );

    if ( str.StartsWith( "op_" ) )
      str = str.Substring( 3 );

    int i = 0, j;
    for ( j = 1; j < str.Length; j++ )
    {
      if ( 'A' <= str[ j ] && str[ j ] <= 'Z' )
      {
        yield return str.Substring( i, j - i );
        i = j;
      }
    }
    if ( i != j )
      yield return str.Substring( i, j - i );
  }

  public static string GetMethodName( this string str )
  {
    if ( str.Contains( "." ) )
    {
      str = str.Split( '.' ).Last();
    }
    return str;
  }
}

次回は bool を返すメソッドとプロパティの接頭辞編。

参考

2009/8/18 追記: 割合と累積の割合も追加。ソースコードもあわせて変更。参考を追加(id:NyaRuRu さんありがとうございます)。