[Перевод] «Это просто мой стиль кода»

Автор этой статьи размышляет о понятии так называемого «стиля программирования» и о том, насколько часто разработчики оправдывают им низкое качество своего кода. Приглашаем вас оценить приведенные автором примеры и поделиться собственным мнением в вопросах стиля!   

51a09cf0c34a7a7d9e158d6d219565a1.png

Стиль кода. Я слышал эти слова, эту глупость в сотне разнообразных вариантов:

»Это просто мой стиль программирования».

»Все пишут код по-разному».

»Так я лучше всего понимаю код».

И так далее, и тому подобное…

Честно говоря, меня бесит, когда я слышу, что разработчик использует одну из этих фраз в качестве оправдания корявости своего кода. Почему? Казалось бы, сущая мелочь. На самом деле, меня раздражает не сама фраза, а глубинный эгоизм, который в ней заключен. Есть только две ситуации, в которых вы вольны писать код так, как вам вздумается: вы пишете лично для себя, и никто больше вашу программу читать не будет ИЛИ речь идет об изолированной среде, например, R&D, где путь проб и ошибок поставлен во главу угла. Но если вы работаете в команде, ваше «я пишу так, как мне удобно» граничит с банальным неуважением.

Аналогия

Идеальная аналогия для иллюстрации проблемы со стилем работы с кодом — это рукописные письма. Давайте вместе взглянем на эту картинку. 

Письмо АПисьмо А

Теперь скажите мне, насколько вам удобно было читать эту записку? Лично для меня верхняя часть выглядит совсем неразборчиво. Я кое-как смог различить несколько слов из середины и ближе к концу, но в целом, если бы кто-то послал мне такой шедевр в конверте, я бы сильно расстроился. Письмо кажется неаккуратным и составленным на скорую руку. Но таков уж его «стиль».

Письмо БПисьмо Б

А вот немного другое письмо.

Его я уже могу прочитать от начала до конца. Некоторые вычурные буквы сложнее разгадать, но тем не менее слова в письме выглядят разборчиво. Согласитесь, оно в целом выглядит опрятнее, чем предыдущее. Но для меня вычурная манера письма — это эквивалент «заумности» в коде. Или, если угодно, применения нестандартных подходов к типовым проблемам. Так ли уж необходим весь этот код, если можно просто-напросто сделать Х?

Письмо СПисьмо С

Лучший вариант я приберег на десерт. 

Это письмо было написано с оглядкой на читателя. С уважением к нему. Оно не вычурное. Оно почти лишено изящества (хотя я вижу творческие нотки в буквах «g» и «y»). Это письмо служит одной четкой цели: донести до читателя информацию — эффективно и профессионально.

Вы уловили суть? Если вы не одиночка или разработчик-любитель, всегда пишете код с расчетом на то, что его будут читать ваши коллеги или вы сами в будущем, когда позабудете принципы и тонкости его работы и станете таким же отстраненным читателем. Языки программирования предназначены в первую очередь для общения с себе подобными, а не с компьютерами, как бы смешно это ни звучало.

Итак, я могу допустить, что у каждого программиста есть «стиль». Весь вопрос в том, является ли ваш стиль «грязным» как в письме А. Или слишком хитрым и вычурным как у автора письма В. Или чистым и профессиональным, но не лишенным доли фантазии, как в примере С.

Несмотря на то, что эта аналогия кажется мне безупречной, давайте рассмотрим несколько примеров кода.

Образцы стиля

Я знаком с языком C#, поэтому все примеры будут приведены на этом языке, но их успешно можно перенести и на другие языки, суть от этого не поменяется.

Письмо А / беспорядочный, грязный стиль

Давайте начнем с экстремального примера. Позвольте мне уточнить, что этот код был придуман специально для статьи, но раньше мне доводилось встречать нечто подобное в реальных проектах. И я работал с людьми, которые начинали свой путь примерно с таких же «полотен» (я очень рад, что они со временем поменяли свои взгляды). Если у вас слабое сердце, лучше пропустите этот пример от греха подальше.

Цель реализованного метода — суммировать количества по имени и поместить результат в базу данных. Господи Боже, от одного взгляда на этот пример у меня болит мозг…

Пример №1

public class record
{
  public string mdate_time = "";
  public string date_time
  {
    get{ return mdate_time; } set { mdate_time = value; }
  }
  public string mname = "";
  public string name
  {
    get { return mname; } set { mname = value; }
  }
  public int mquantity = 0;
  public int qty
  {
    get { return mquantity; }
    set {
      if (value < 0) value = 0;
      mquantity = value;
    }
  }
}

public void RunProc(List input)
{
  records r;
  SqlConnection s;
  SqlCommand cmd;
  int index;
  int index2;
  int foundIndex;
  var grp = new List();
  index = 0;
  while (index < input.Count)
  {
    r = input[index];
    index2 = 0;
    foundIndex = -1;
    while (index2 < grp.Count)
    {
      if (grp[index2].name == input[index].name)
      {
        foundIndex = index2;
        break;
      }
      index2++;
    }
    if (foundIndex > -1)
    {
      grp[foundIndex].qty = grp[foundIndex].qty + input[index].qty;
    } else grp.Add(input[index]);
    index++;
  }
  index = 0;
  while (index < grp.Count)
  {
    r = grp[index];
    s = new SqlConnection(connnection_t); s.Open();
    try
    {
      cmd = new SqlCommand("insert into [item_table] (time, product_name, qty) values (@time, @pn, @q)", s);
      cmd.Parameters.AddWithValue("time", DateTime.Parse(r.date_time));
      cmd.Parameters.AddWithValue("pn", r.name);
      cmd.Parameters.AddWithValue("q", r.qty);
      cmd.ExecuteNonQuery();
    }
    catch (Exception)
    { }
    finally{ s.Close();    }
    index++;  }
}

Повторюсь, эта конкретная реализация — всего лишь плод моего воображения, но один мой знакомый вполне мог бы разродиться чем-то подобным, честное слово. У меня даже есть свидетели…

Такой код — это демонстрация неуважения к окружающим и фундаментального непонимания того, как устроен язык. Да, технически код работает. Но написать рабочую программу может даже 12-летний ребенок (позже я приведу личный пример).

Письмо B / Слегка »заумный» стиль

Код из этого примера выполняет точно такую же задачу, но его автор явно заигрывается сам с собой. Да, здесь всё написано гораздо понятнее, чем выше, однако у меня все еще есть вопросы к читабельности этого образца.

Пример №2

public class ProductRecord
{
  public DateTime Timestamp { get; set; }
  public string Name  {get; set; }
  public int Quantity { get; set; }
}

public void GroupAndInsertProductRecords(IEnumerable input)
{
  const string SQL_TEMPLATE = "INSERT INTO [ProductCount] (Timestamp, Name, Quantity) VALUES (";
  var grouped = input.GroupBy(r => r.Name)
    .Select(g => new {
      Timestamp = g.First().Timestamp,
      Name = g.Key,
      Quantity = g.Sum(e => e.Quantity)
    }).ToArray();
  int entryCount = grouped.Count();

  var SQL = string.Join("\n", grouped.Select((g, index) => SQL + $"@Timestamp{index},@Name{index},@Quantity{index});"));
  using (var sqlConn = new SqlConnection(connectionString))
  {
    sqlConn.Open();
    using (var cmd = new SqlCommand(SQL, sqlConn))
    {
      for(int i = 0; i < entryCount; i++)
      {
        cmd.Parameters.AddWithValue($"@Timestamp{i}", grouped[i].Timestamp);
        cmd.Parameters.AddWithValue($"@Name{i}", grouped[i].Timestamp);
        cmd.Parameters.AddWithValue($"@Quantity{i}", grouped[i].Timestamp);
      }
      cmd.ExecuteNonQuery();
    }
  }
}

Такой код кажется мне гораздо более сносным. Да, потребуется минута-другая, чтобы понять, что происходит с SQL_TEMPLATE и какая цель здесь достигается. Но этот разработчик предлагает более производительную реализацию, чем предыдущий, хотя и с интересным подходом к конкатенации строк.

Конечно, можно еще лучше оптимизировать запрос. Нет никакой необходимости использовать INSERT несколько раз. Вместо этого можно просто разбить всё на отдельные строки после ключевого слова VALUES, и это сработает. Автор прибавляет значения в цикле, прежде чем сделать запрос на сервер.

Опять же, не самое худшее, но и не самое лучшее решение. Местами оно выглядит странно или заумно и требует дополнительного времени на понимание. Однако оно в корне отличается от письма А.

Письмо C / Чисто и ясно

Пришло время перейти к действительно неплохому варианту. Давайте напишем производительный и чистый код, который наш коллега-разработчик сможет прочитать и быстро понять — фактически «отполируем» вариант B.

Пример №3

public class ProductRecord
{
  public DateTime Timestamp { get; set; }
  public string Name  {get; set; }
  public int Quantity { get; set; }
}

public void GroupAndInsertProductRecords(IEnumerable records)
{
  ProductRecord[] groupedRecords = GroupProductRecordsByName(records);

  using (var sqlConn = OpenSqlConnection())
  using (var sqlCommand = BuildBulkProductCountCommand(sqlConn, groupedRecords))
  {
    sqlCommand.ExecuteNonQuery();
  }  
}

private ProductRecord[] GroupProductRecordsByName(IEnumerable records)
  => records
    .GroupBy(r => r.Name)
    .Select(grp => new ProductRecord {
      Timestamp = grp.First().Timestamp,
      Name = grp.Key,
      Quantity = grp.Sum(e => e.Quantity)
    })
    .ToArray();

private SqlConnection OpenSqlConnection()
{
  var sqlConnection = new SqlConnection(_ConnectionString);
  sqlConnection.Open();
  return sqlConnection;
}

private SqlCommand BuildBulkProductCountCommand(SqlConnection sqlConnection, ProductRecord[] groups)
{
  StringBuilder commandTextBuilder = new StringBuilder(@"
    INSERT INTO [ProductCount] (Timestamp, Name, Quantity)
    VALUES
  ");

  var command = new SqlCommand();
  command.Connection = sqlConnection;

  for(int i; i < groups.Length - 1; i++)
  {
    commandTextBuilder
      .Append(AddParametersAndGenerateValueRow(groups[i], i, command))
      .AppendLine(",");
  }
  
  command.CommandText = commandTextBuilder
    .AppendLine(AddParametersAndGenerateValueRow(groups[i], i, command))
    .ToString();

  return command;
}

private string AddParametersAndGenerateValueRow(ProductRecord group, int index, SqlCommand command)
{
  command.Parameters.AddWithValue($"@Timestamp{index}", group.Timestamp);
  command.Parameters.AddWithValue($"@Name{index}", group.Name);
  command.Parameters.AddWithValue($"@Quantity{index}", group.Quantity);

  return $"(@Timestamp{index}, @Name{index}, @Quantity{index})";
}

Ого! Наш код вырос практически вдвое по сравнению с вариантом B. Дискуссия объявляется открытой! Является ли количество строк важной метрикой? Кто-то и вовсе ставит ее во главу угла. Я в целом согласен с ними, потому что больше количество строк зачастую сигнализирует о неэффективности. Но всегда ли это проблема, от которой нужно бежать как от огня?

Вопрос в том, можно ли переиспользовать код, абстрагировать или иным образом «отложить» его в сторону, чтобы читатель мог обращаться к нему только по мере надобности. Алгоритм, используемый в «чистовой» реализации, по сути, ничем не отличается от второго варианта. Единственные существенные различия — это использование методов и StringBuilder (который действительно дает небольшое преимущество).

Теперь давайте взглянем на код под другим углом. Вместо длины сосредоточимся только на публичных методах из образцов B и C.

Образец B (публичный метод)

public void GroupAndInsertProductRecords(IEnumerable input)
{
  const string SQL_TEMPLATE = "INSERT INTO [ProductCount] (Timestamp, Name, Quantity) VALUES (";
  var grouped = input.GroupBy(r => r.Name)
    .Select(g => new {
      Timestamp = g.First().Timestamp,
      Name = g.Key,
      Quantity = g.Sum(e => e.Quantity)
    }).ToArray();
  int entryCount = grouped.Count();

  var SQL = string.Join("\n", grouped.Select((g, index) => SQL + $"@Timestamp{index},@Name{index},@Quantity{index});"));
  using (var sqlConn = new SqlConnection(connectionString))
  {
    sqlConn.Open();
    using (var cmd = new SqlCommand(SQL, sqlConn))
    {
      for(int i = 0; i < entryCount; i++)
      {
        cmd.Parameters.AddWithValue($"@Timestamp{i}", grouped[i].Timestamp);
        cmd.Parameters.AddWithValue($"@Name{i}", grouped[i].Timestamp);
        cmd.Parameters.AddWithValue($"@Quantity{i}", grouped[i].Timestamp);
      }
      cmd.ExecuteNonQuery();
    }
  }
}

Образец C (публичный метод)

public void GroupAndInsertProductRecords(IEnumerable records)
{
  ProductRecord[] groupedRecords = GroupProductRecordsByName(records);

  using (var sqlConn = OpenSqlConnection())
  using (var sqlCommand = BuildBulkProductCountCommand(sqlConn, groupedRecords))
  {
    sqlCommand.ExecuteNonQuery();
  }  
}

Какие метод легче прочитать и понять? Скрещу пальцы и скажу, что вы выбрали вариант C. Реальность такова, что, когда начинаешь копаться в коде, в первую очередь смотришь на публичные методы, особенно при отладке. Приватные методы на первых порах вам не видны, поскольку именно public’и являются точкой входа. Даже при обычном чтении кода публичные методы бросаются в глаза раньше приватных. Поэтому логично предположить, что (в идеальном мире) первый метод, который вам встретился, ведет вас к следующему этапу, а не содержит весь комплекс реализации в непонятном «заумном» виде.

Когда я читаю образец B, для его понимания мне приходится немного поднапрячься. Фактически, мне нужно прочитать весь метод от начала до конца, чтобы понять, что в нем происходит. Но, кхм, это же более компактный подход, к тому же «сжатый» весьма изобретательным способом.

Читая образец С, я понимаю, что для дальнейшего изучения мне понадобится открыть только два метода: GroupProductRecordsByName и BuildBulkProductCountCommand. Названия этих методов ясны и лаконичны. Переходя к ним, вы сразу понимаете, что они делают. Иными словами, публичный метод достаточно информативен, и вы сразу же понимаете, что делает программа.

Поэтому несмотря на то, что в примере C, формально больше строк кода (потенциально даже чуть более «сложного»), читать его легче. Вы можете двигаться по коду и точно знать, что происходит на каждом этапе. Написание такого кода занимает больше времени, требуется определить имена функций и грамотно разложить методы. Но такой код не пишется для сиюминутной выгоды. Хорошо написанная программа сэкономит массу времени и вам будущему, и следующему человеку, которому придется работать с вашей кодовой базой.

Заключение

Я решил быть с вами откровенным. В настоящее время я отказался от подобного подхода к кодингу. Вариант С неэффективен в своей текущей реализации. В реальности я бы использовал Dapper или любой другой ORM и получил бы гораздо более лаконичное решение. Но речь не об этом.

Мы должны проявлять уважение и относиться к нашим коллегам-разработчикам профессионально. Мы должны расти вместе и подталкивать друг друга к тому, чтобы в один прекрасный день стать прозаиками или поэтами компьютерной эры. Работая над кодом, стремитесь к чувству гордости: за названия переменных и методов, за интервалы и переносы строк. Гордитесь своей работой!

Бонус!

В заключение мне хотелось бы прикрепить несколько примеров кода, чтобы показать, как УЖАСНО и нечитабельно я писал прежде. Ниже приведены реальные программы, которые я написал на разных этапах своего развития в области разработки ПО. Не стесняйтесь в выражениях! Я заслуживаю каждого камня, брошенного в меня за эти строчки… :)

Даже в самом свежем своем коде (написанном в прошлом году) я вижу точки роста. Важная ремарка: речь идет не о недостижимом «совершенстве», а о моем процессе совершенствования и взросления в качестве разработчика…

Кстати, вот вам небольшая игра: попробуйте понять и написать в комментариях, что делает та или иная программа и как работают мои алгоритмы. Как быстро вы во всем разберетесь?

Наслаждайтесь!

Military Strikes (игра на VB6) — 1999 (автору 12 лет)

Option Strict Off
Option Explicit On
Imports Artinsoft.VB6.Gui
Imports Artinsoft.VB6.Utils
Imports Microsoft.VisualBasic
Imports Military_Game_UpgradeSupport.UpgradeStubs
Imports System
Imports System.Drawing
Imports System.Windows.Forms
Partial Friend Class Form1
	Inherits System.Windows.Forms.Form
	'Variables
	Dim CurrentTurn As Integer
	Dim NewArmy() As Module1.Army = ArraysHelper.InitializeArray(Of Module1.Army)(101)
	Dim EnemyArmy() As Module1.Army = ArraysHelper.InitializeArray(Of Module1.Army)(101)
	Dim CurNumArmies As Integer
	Dim Ifclicked As Boolean
	Dim CurrentClicked As Integer
	Dim NewReg As Boolean
	Public Sub New()
		MyBase.New()
		If m_vb6FormDefInstance Is Nothing Then
			If m_InitializingDefInstance Then
				m_vb6FormDefInstance = Me
			Else
				Try
					'For the start-up form, the first instance created is the default instance.
					If System.Reflection.Assembly.GetExecutingAssembly.EntryPoint.DeclaringType Is Me.GetType Then
						m_vb6FormDefInstance = Me
					End If

				Catch
				End Try
			End If
		End If
		'This call is required by the Windows Form Designer.
		InitializeComponent()
		ReLoadForm(False)
	End Sub

	Private Sub Command1_Click(ByVal eventSender As Object, ByVal eventArgs As EventArgs) Handles Command1.Click
		NewReg = True
        Me.ForeColor = Color.Blue
        'Dim g As Graphics = Graphics.FromImage(Pic.Image)
        'g.DrawEllipse(Pens.Black, VB6.TwipsToPixelsX(VB6.FromPixelsUserX(Command1.Left, 0, 7620, VB6.TwipsToPixelsX(7620)) + VB6.FromPixelsUserWidth(Command1.Width, 7620, VB6.TwipsToPixelsX(7620)) + 16), VB6.TwipsToPixelsY(VB6.FromPixelsUserY(Command1.Top, 0, 4935, VB6.TwipsToPixelsY(4935)) + (VB6.FromPixelsUserHeight(Command1.Height, 4935, VB6.TwipsToPixelsY(4935)) / 2)), VB6.TwipsToPixelsX(12))
		Me.ForeColor = Color.Black
	End Sub

	Private Sub EndTurn_Click(ByVal eventSender As Object, ByVal eventArgs As EventArgs) Handles EndTurn.Click
		If CurNumArmies < 1 Then
			MessageBox.Show("Need Army Corps to continue to end a turn", Application.ProductName)
			Exit Sub
		End If
		'*** Increments [Turn] Variable
		CurrentTurn += 1
		lblNumberTurns.Text = Conversion.Str(CurrentTurn)
		'***
		ResetMoves()
		RewriteInfoBox()
	End Sub

	'Moves the Currently selected Corp/regiment in the specified
	'direction.
	Private Sub DirectionMove_Click(ByVal eventSender As Object, ByVal eventArgs As EventArgs) Handles _DirectionMove_3.Click, _DirectionMove_2.Click, _DirectionMove_1.Click, _DirectionMove_0.Click
		Dim Index As Integer = Array.IndexOf(DirectionMove, eventSender)
		If Ifclicked Then
			If NewArmy(CurrentClicked).MovesLeft > 0 Then
				Select Case Index
					Case 0 'North
						With NewArmy(CurrentClicked)
							.Y1 -= NewArmy(CurrentClicked).UnitsHigh
							.Y2 -= NewArmy(CurrentClicked).UnitsHigh
							If .Y1 < 0 Then
								.Y1 = 0
								.Y2 = NewArmy(CurrentClicked).UnitsHigh
								.MovesLeft = .MovesLeft
							Else
								.MovesLeft = CShort(.MovesLeft - 1)
							End If
						End With
						RedrawArmies()
						RewriteInfoBox()
					Case 1 'South
						With NewArmy(CurrentClicked)
							.Y1 += NewArmy(CurrentClicked).UnitsHigh
							.Y2 += NewArmy(CurrentClicked).UnitsHigh
							If .Y2 > 2000 Then
								.Y1 = 2000 - NewArmy(CurrentClicked).UnitsHigh
								.Y2 = 2000
								.MovesLeft = .MovesLeft
							Else
								.MovesLeft = CShort(.MovesLeft - 1)
							End If
						End With
						RedrawArmies()
						RewriteInfoBox()
					Case 2 'East
						With NewArmy(CurrentClicked)
							.X1 += NewArmy(CurrentClicked).UnitsWide
							.X2 += NewArmy(CurrentClicked).UnitsWide
							If .X2 > 2000 Then
								.X1 = 2000 - NewArmy(CurrentClicked).UnitsWide
								.X2 = 2000
								.MovesLeft = .MovesLeft
							Else
								.MovesLeft = CShort(.MovesLeft - 1)
							End If
						End With
						RedrawArmies()
						RewriteInfoBox()
					Case 3 'West
						With NewArmy(CurrentClicked)
							.X1 -= NewArmy(CurrentClicked).UnitsWide
							.X2 -= NewArmy(CurrentClicked).UnitsWide
							If .X1 < 0 Then
								.X1 = 0
								.X2 = NewArmy(CurrentClicked).UnitsWide
								.MovesLeft = .MovesLeft
							Else
								.MovesLeft = CShort(.MovesLeft - 1)
							End If
						End With
						RedrawArmies()
						RewriteInfoBox()
				End Select
			End If
		End If
	End Sub

	'UPGRADE_WARNING: (2080) Form_Load event was upgraded to Form_Load method and has a new behavior. More Information: http://www.vbtonet.com/ewis/ewi2080.aspx
    Private Sub Form_Load() Handles Me.Load
        CurrentTurn = 1
        lblNumberTurns.Text = Conversion.Str(CurrentTurn)
        NewReg = False
        CurNumArmies = 0
        Ifclicked = False
        FirstX(0) = -1
        FirstY(0) = -1
        Me.Text = "Military Strikes - " & CountryName
        '*** Loads Forms
        Dim tempLoadForm As frmPopDetails = frmPopDetails.DefInstance
        Dim tempLoadForm2 As frmEconDetails = frmEconDetails.DefInstance
        Dim tempLoadForm3 As frmMilDetails = frmMilDetails.DefInstance
        Dim tempLoadForm4 As Form2 = Form2.DefInstance
        Dim tempLoadForm5 As frmInfo = frmInfo.DefInstance
        SetInitialVariables()
        ReWriteInfoFormText()
        Pic.Image = New Bitmap(Pic.Width, Pic.Height)
        frmInfo.DefInstance.Left = Me.Left + Me.Width
        frmInfo.DefInstance.Top = Me.Top
        frmInfo.DefInstance.Show()
        Form2.DefInstance.Hide()
        frmMilDetails.DefInstance.Hide()
        frmPopDetails.DefInstance.Hide()
        frmEconDetails.DefInstance.Hide()
        '***
    End Sub

	'Places Army on Picture Box
	Private Sub Pic_MouseDown(ByVal eventSender As Object, ByVal eventArgs As MouseEventArgs) Handles Pic.MouseDown
		Dim Button As Integer = CInt(eventArgs.Button)
		Dim Shift As Integer = Control.ModifierKeys \ &H10000
		Dim x As Single = VB6.FromPixelsUserX(eventArgs.X, 0, 7620, VB6.TwipsToPixelsX(7620))
		Dim y As Single = VB6.FromPixelsUserY(eventArgs.Y, 0, 4935, VB6.TwipsToPixelsY(4935))
		Dim LEFTCORP, RIGHTCORP, ArmyNumber As Integer
		Ifclicked = False
		Matched(ArmyNumber, x, y)
		If ArmyNumber > 0 Then
			NewReg = False
			ControlHelper.Cls(Me)
			Ifclicked = True
			CurrentClicked = ArmyNumber
			RedrawArmies()
			With NewArmy(ArmyNumber)
                'UPGRADE_ISSUE: (2064) PictureBox property Pic.ForeColor was not upgraded. More Information: http://www.vbtonet.com/ewis/ewi2064.aspx
                Dim g As Graphics = Graphics.FromImage(Pic.Image)
				Pic.setForeColor(Color.Blue)
                'UPGRADE_ISSUE: (2064) PictureBox method Pic.Circle was not upgraded. More Information: http://www.vbtonet.com/ewis/ewi2064.aspx
                g.DrawEllipse(Pens.Blue, New Rectangle(.X1 + (.UnitsWide / 2), .Y1 + (.UnitsHigh / 2), 4, 4))
                'Pic.Circle(.X1 + (.UnitsWide / 2), .Y1 + (.UnitsHigh / 2), 4)
				'UPGRADE_ISSUE: (2064) PictureBox property Pic.ForeColor was not upgraded. More Information: http://www.vbtonet.com/ewis/ewi2064.aspx
                'Pic.setForeColor(Color.Black)
                g.Dispose()
				RewriteInfoBox()
			End With
			Exit Sub
		End If
		If NewReg Then
			CurNumArmies += 1
			Form2.DefInstance.ShowDialog()
			With NewArmy(CurNumArmies)
				.UnitsHigh = CShort(UnitHigh)
				.UnitsWide = CShort(UnitWide)
				DetermineClosetBlock(x, y)
				AddArmy(.X1, .X2, .Y1, .Y2, MilType, NewArmy(CurNumArmies))
				FirstX(CurNumArmies) = .X1
				FirstY(CurNumArmies) = .Y1
				.Moves = CShort(UnitMove)
				.MovesLeft = CShort(UnitMove)
				.Population = CShort(UnitPop)
				.Attack = CShort(UnitAttack)
				.Defense = CShort(UnitDefense)
				.Range = CShort(UnitRange)
				ReWriteInfoFormText()
				BlockCorpTop(.BlockX, .BlockY) = .MilitaryType 'Sets Type of Corp on block
				BlockCorpArmy(.BlockX, .BlockY) = CurNumArmies ' Sets Army Number in Block

				MapType(.BlockX * 2, .BlockY) = .MilitaryType 'Sets current army type in that block
				MapType(.BlockX * 2 + 1, .BlockY) = .MilitaryType ' each corp takes up two map blocks

				MapArmy(.BlockX * 2, .BlockY) = CurNumArmies 'set current army in block
				MapArmy(.BlockX * 2 + 1, .BlockY) = CurNumArmies ' each corp takes up two map blocks

				MapBlock(.BlockX * 2, .BlockY) = LEFTCORP 'Sets which part of the corp is inside the map block
				MapBlock(.BlockX * 2 + 1, .BlockY) = RIGHTCORP '2nd part of corp
			End With
		End If
		NewReg = False
		ControlHelper.Cls(Me)
	End Sub

	Public Sub RedrawArmies()
		'UPGRADE_ISSUE: (2064) PictureBox method Pic.Cls was not upgraded. More Information: http://www.vbtonet.com/ewis/ewi2064.aspx
		Pic.Cls()
		For i As Integer = 1 To CurNumArmies
			With NewArmy(i)
				Using g As Graphics = Pic.CreateGraphics()
                    g.DrawRectangle(New Pen(ColorTranslator.FromOle(NewArmy(i).Color)), New Rectangle(New Point(.X1, .Y1), New Point(.X2, .Y2)))
				End Using
			End With
		Next i
		If Ifclicked Then
			With NewArmy(CurrentClicked)
				'UPGRADE_ISSUE: (2064) PictureBox method Pic.Circle was not upgraded. More Information: http://www.vbtonet.com/ewis/ewi2064.aspx
				Pic.Circle(.X1 + (.UnitsWide / 2), .Y1 + (.UnitsHigh / 2), 4, ColorTranslator.ToOle(Color.Blue))
			End With
		End If
	End Sub

	Public Sub Matched(ByRef ArmyNumber As Integer, ByVal Xclick As Single, ByVal Yclick As Single)
		For i As Integer = 1 To CurNumArmies
			With NewArmy(i)
				If Xclick > .X1 And Xclick < .X2 Then
					If Yclick > .Y1 And Yclick < .Y2 Then
						ArmyNumber = i
						Exit Sub
					End If
				End If
			End With
		Next i
		ArmyNumber = -1
	End Sub

	Public Sub RewriteInfoBox()
		With NewArmy(CurrentClicked)
			InfoBox.Text = "Coords: (" & Conversion.Str(.X1) & "," & Conversion.Str(.Y1) & ")" & Environment.NewLine &  _
			               "Military Type: " & .Name & " (" & Conversion.Str(.MilitaryType) & ")" & Environment.NewLine &  _
			               "Moves: " & Conversion.Str(.MovesLeft) & " / " & Conversion.Str(.Moves) & Environment.NewLine &  _
			               "Corp Population: " & StringsHelper.Format(.Population, "###,###") & Environment.NewLine &  _
			               "Att / Def: " & Conversion.Str(.Attack) & " / " & Conversion.Str(.Defense)
		End With
	End Sub

	Public Sub DetermineClosetBlock(ByVal x As Single, ByVal y As Single)
		For Xtest As Integer = 0 To 9
			For Ytest As Integer = 0 To 19
				If x > (Xtest * 200) And x < (Xtest * 200) + 200 Then
					If y > (Ytest * 100) And y < (Ytest * 100) + 100 Then
						NewArmy(CurNumArmies).X1 = Xtest * 200
						NewArmy(CurNumArmies).X2 = (Xtest * 200) + 200
						NewArmy(CurNumArmies).Y1 = Ytest * 100
						NewArmy(CurNumArmies).Y2 = (Ytest * 100) + 100
						NewArmy(CurNumArmies).BlockX = CShort(Xtest)
						NewArmy(CurNumArmies).BlockY = CShort(Ytest)
					End If
				End If
			Next Ytest
		Next Xtest
	End Sub

	Public Sub ResetMoves()
		For i As Integer = 1 To CurNumArmies
			NewArmy(i).MovesLeft = NewArmy(i).Moves
		Next i
    End Sub

	Private Sub Form1_MouseMove(ByVal eventSender As Object, ByVal eventArgs As MouseEventArgs) Handles MyBase.MouseMove
		Dim Button As Integer = CInt(eventArgs.Button)
		Dim Shift As Integer = Control.ModifierKeys \ &H10000
		Dim x As Single = VB6.FromPixelsUserX(eventArgs.X, 0, 7620, VB6.TwipsToPixelsX(7620))
		Dim y As Single = VB6.FromPixelsUserY(eventArgs.Y, 0, 4935, VB6.TwipsToPixelsY(4935))
		'If Ifclicked = True Then
		'    With NewArmy(CurrentClicked)
		'        .X1 = X
		'        .Y1 = Y
		'        .X2 = X + 150
		'        .Y2 = Y + 100
		'    End With
		'    Cls
		'    Call RedrawArmies
		'End If
	End Sub

	Private Sub Form1_MouseUp(ByVal eventSender As Object, ByVal eventArgs As MouseEventArgs) Handles MyBase.MouseUp
		Dim Button As Integer = CInt(eventArgs.Button)
		Dim Shift As Integer = Control.ModifierKeys \ &H10000
		Dim x As Single = VB6.FromPixelsUserX(eventArgs.X, 0, 7620, VB6.TwipsToPixelsX(7620))
		Dim y As Single = VB6.FromPixelsUserY(eventArgs.Y, 0, 4935, VB6.TwipsToPixelsY(4935))
		'Ifclicked = False
	End Sub

	Private Sub Form1_Closed(ByVal eventSender As Object, ByVal eventArgs As EventArgs) Handles MyBase.Closed
		Environment.Exit(0)
	End Sub

	Private Sub Timer1_Tick(ByVal eventSender As Object, ByVal eventArgs As EventArgs) Handles Timer1.Tick
		frmInfo.DefInstance.Left = Me.Left + Me.Width
		frmInfo.DefInstance.Top = Me.Top
	End Sub

End Class

SPICE Model Engine (VB.NET) — 2006

Imports Errors
Imports System.IO
Imports System.Text.RegularExpressions
Imports File_Import_Engine

Public Module MModel
#Region "Constants"
    Private Const cTOTAL_VAR_PARAMETERS = 3
    Private Const cMAX_NUM_MODEL_LIBRARIES = 10
#End Region

#Region "Model Library Functions"
    Public ModelLibraries() As cModelLibrary
    Private NumModelLibraries As Integer = 0

    'Create a New Model Library
    Public Sub AddModelLibrary(ByVal tModel() As cModel, ByVal tName As String)
        'Error Checking
        If NumModelLibraries = cMAX_NUM_MODEL_LIBRARIES Then
            cWarning.AddWarning(105, "MModel", "Maximum Number of Libraries Reached")
            Exit Sub
        End If
        If IsNothing(tModel) Then Exit Sub

        ReDim Preserve ModelLibraries(NumModelLibraries)
        ModelLibraries(NumModelLibraries) = New cModelLibrary(tName)
        ModelLibraries(NumModelLibraries).Add(tModel)
        NumModelLibraries += 1
    End Sub

    Public Function SearchModelLibrary(ByVal tIndex As Integer, ByVal tName As String) As cModel
        If tIndex > NumModelLibraries - 1 Then Return Nothing

        Dim tFoundModel As cModel = ModelLibraries(tIndex).Model_Symbol(tName)
        If IsNothing(tFoundModel) Then
            cError.AddError(531, "MModel", "Model not found in '" & ModelLibraries(tIndex).LibraryName & "' Library")
            Return Nothing
        End If


        Return tFoundModel
    End Function

    Public Function SearchAllLibraries(ByVal tName As String) As cModel
        If NumModelLibraries < 1 Then Return Nothing
        For i As Integer = 0 To NumModelLibraries - 1
            Dim tFoundModel As cModel = SearchModelLibrary(i, tName)
            If Not IsNothing(tFoundModel) Then Return tFoundModel
        Next
        cError.AddError(532, "MModel", "Model Not Found in Any Library")
        Return Nothing
    End Function
#End Region

    Public Function ImportModelFile(ByVal tFile As String) As cModel()
        Dim strComponents() As ioComponentLibrary_ComponentBlock = ImportComponentLibrary(tFile)
        If IsNothing(strComponents) Then Return Nothing

        Dim numModels As Long = strComponents.Length

        'Model Information Variables
        Dim Models(numModels - 1) As cModel

        For iComp As Integer = 0 To strComponents.Length - 1
            With strComponents(iComp)
                Models(iComp) = New cModel(.Name, .Symbol, ArrayListToStringArray(.Nodes))
                For iSim As Integer = 0 To .SimBlocks.Length - 1
                    With .SimBlocks(iSim)
                        Dim tSimType As SimulationType = ParseSimType(.SimulationType)
                        If tSimType = -1 Then Return Nothing
                        For iEntry As Long = 0 To .Netlist_Entries.Count - 1
                            Models(iComp).AddEntry(tSimType, .Netlist_Entries(iEntry))
                        Next
                        For iEq As Integer = 0 To .VARS.Count - 1
                            Models(iComp).AddEquation(tSimType, .VARS(iEq), .VAR_EQNS(iEq), .VAR_ICS(iEq))
                        Next
                    End With
                Next
                For iPar As Integer = 0 To .VarNames.Count - 1
                    Models(iComp).AddParameter(.VarNames(iPar), "0", ParseUnit(.VarTypes(iPar)))
                    Models(iComp).Parameters.Description(iPar) = .VarDescs(iPar)
                Next
            End With
        Next

        Return Models
    End Function

    Private Function ParseVarLine(ByVal tNameText As String, ByVal tTypeText As String, ByVal tDescText As String, ByVal tFile As String, ByVal numLines As Long) As cParameters
        Dim tParams As New cParameters()
        If tNameText.Trim = "" Or tTypeText.Trim = "" Or tDescText.Trim = "" Then Return tParams

        Dim hasBracks As Boolean = False
        Dim hasBrackName As Boolean = HasBrackets(tNameText)
        Dim hasBrackType As Boolean = HasBrackets(tTypeText)
        Dim hasBrackDesc As Boolean = HasBrackets(tDescText)

        'Check for Bracket Consistency
        If (hasBrackName And hasBrackType And hasBrackDesc) Then
            hasBracks = True
        ElseIf Not (hasBrackName Or hasBrackType Or hasBrackDesc) Then
            hasBracks = False
        Else
            cError.AddError(529, "MModel", "Invalid Bracket Parsing during Library Import of file '" & tFile & "' on Line " & numLines & "")
            Return tParams
        End If

        If hasBracks Then
            Dim Names() As String = ParseBrackets(tNameText)
            Dim Types() As eUnitCategory = ParseUnitArray(ParseBrackets(tTypeText))
            Dim Descs() As String = ParseBrackets(tDescText)
            Dim tLength As Integer = Names.Length

            If IsNothing(Types) Then Return tParams

            If tLength <> Types.Length Or tLength <> Descs.Length Then : cError.AddError(530, "MModel", "Invalid Number of Values in Brackets during Library Import of File '" & tFile & "' on Line " & numLines) : Return tParams : End If
            For i As Integer = 0 To tLength - 1
                tParams.AddParameter(RemoveQuotes(Names(i)), RemoveQuotes(Descs(i)), Types(i))
            Next
        Else
            Dim tType As eUnitCategory = ParseUnit(tTypeText)
            If tType = eUnitCategory.eError Then Return tParams
            tParams.AddParameter(RemoveQuotes(tNameText), RemoveQuotes(tDescText), tType)
        End If
        Return tParams
    End Function

    Private Function ParseBrackets(ByVal str As String) As String()
        Dim tMatches As MatchCollection = Regex.Matches(str, "([\w\-\+\.\$\\\/\*\^\(\)]+)|(""[^""\r\n]*"")")
        Dim tReturn(tMatches.Count - 1) As String
        For i As Integer = 0 To tMatches.Count - 1
            tReturn(i) = tMatches.Item(i).Value
        Next
        Return tReturn
    End Function

    Private Function HasBrackets(ByVal str As String) As Boolean
        If Strings.InStr(str, "{") = 0 Or Strings.InStr(str, "}") = 0 Then Return False 'Check for the characters first
        str = str.Trim
        If Strings.Left(str, 1) <> "{" Or Strings.Right(str, 1) <> "}" Then Return False 'Make sure they're on the ends
        Return True
    End Function

    Private Function ParseUnit(ByVal tUnitName As String) As eUnitCategory
        Dim str As String = tUnitName.Trim.ToUpper
        Select Case str
            Case "RES", "RESISTANCE"
                Return eUnitCategory.Resistance
            Case "CAP", "CAPACITANCE"
                Return eUnitCategory.Capacitance
            Case "IND", "INDUCTANCE"
                Return eUnitCategory.Inductance
            Case "NON", "UNITLESS"
                Return eUnitCategory.Unitless
            Case "STR", "STRING"
                Return eUnitCategory.eString
            Case "TEM", "TEMP", "TEMPERATURE"
                Return eUnitCategory.Temperature
            Case "LEN", "LENGTH"
                Return eUnitCategory.Length
            Case "ARA", "AREA"
                Return eUnitCategory.Area
            Case "VOL", "VOLUME"
                Return eUnitCategory.Volume
            Case "TIM", "TIME"
                Return eUnitCategory.Time
            Case "FRQ", "FREQ", "FREQUENCY"
                Return eUnitCategory.Frequency
            Case "RIO", "RAT", "RATIO", "DB"
                Return eUnitCategory.Ratio
            Case "CUR", "CURRENT"
                Return eUnitCategory.Current
            Case "VOL", "VOLTAGE"
                Return eUnitCategory.Voltage
        End Select
        Return eUnitCategory.eError
    End Function

    Private Function ParseUnitArray(ByVal tUnitName As String()) As eUnitCategory()
        If IsNothing(tUnitName) Then Return Nothing

        Dim tLength As Integer = tUnitName.Length
        If tLength = 0 Then Return Nothing

        Dim rtnCategories(tLength - 1) As eUnitCategory
        For i As Integer = 0 To tLength - 1
            rtnCategories(i) = ParseUnit(tUnitName(i))
        Next
        Return rtnCategories
    End Function

    Private Function RemoveQuotes(ByVal str As String) As String
        If Strings.Left(str, 1) = ControlChars.Quote Then str = Strings.Right(str, str.Length - 1)
        If Strings.Right(str, 1) = ControlChars.Quote Then str = Strings.Left(str, str.Length - 1)
        Return str
    End Function

    Private Function ParseSimType(ByVal str As String) As SimulationType
        str = str.ToUpper.Trim
        Select Case str
            Case "TRAN", "TRANS", "TRANSIENT"
                Return SimulationType.eTransient
            Case "AC"
                Return SimulationType.eAC
            Case "DC"
                Return SimulationType.eDC
            Case "YELD", "YIELD"
                Return SimulationType.eYield
            Case "NOIS", "NOISE"
                Return SimulationType.eNoise
            Case "SPAR", "SSIG"
                Return SimulationType.eSmallSignal
            Case "LSIG", "HARM"
                Return SimulationType.eLargeSignal
        End Select
        Return -1
    End Function

    'Assuming Arraylist contains only strings
    Private Function ArrayListToStringArray(ByVal tList As ArrayList) As String()
        If IsNothing(tList) Then Return Nothing

        Dim tReturn(tList.Count - 1) As String
        For i As Integer = 0 To tList.Count - 1
            tReturn(i) = tList(i)
        Next
        Return tReturn
    End Function
End Module

Система инвентаризации магазина карточек (C#.NET) — 2015

using CornerMagic.Persistence;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CornerMagic.Models;
using System.Data.Entity;

namespace CornerMagic.Data.SqlServer.Repositories
{
    internal class GameRepository : BaseRepository, IGameRepository
    {
        public GameRepository(CornerMagicContext context) : base(context) { }

        public void Delete(Game game)
        {
            if (game.Id == 0) return;

            Game foundGame = Context.Games.Find(game.Id);
            if (foundGame != null)
                Context.Games.Remove(foundGame);
        }

        public async Task DeleteAsync(Game game)
        {
            if (game.Id == 0) return;

            Game foundGame = await Context.Games.FindAsync(game.Id);
            if (foundGame != null)
                Context.Games.Remove(foundGame);
        }

        public Game FindById(int Id)
        {
            return Context.Games.FirstOrDefault(g => g.Id == Id);
        }

        public async Task FindByIdAsync(int Id)
        {
            return await Context.Games.FirstOrDefaultAsync(g => g.Id == Id);
        }

        public Game FindByName(string name)
        {
            Game rtn = Context.Games.FirstOrDefault(g => g.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
            if (rtn != null)
                Context.Games.Attach(rtn);
            return rtn;
        }

        public async Task FindByNameAsync(string name)
        {
            Game rtn = await Context.Games.FirstOrDefaultAsync(g => g.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
            if (rtn != null)
                Context.Games.Attach(rtn);
            return rtn;
        }

        public IEnumerable GetAll()
        {
            return Context.Games.OrderBy(x => x.Name).ToArray();
        }

        public async Task> GetAllAsync()
        {
            return await Context.Games.OrderBy(x => x.Name).ToArrayAsync();
        }

        public int GetNumberOfGames()
        {
            return Context.Games.Count();
        }

        public async Task GetNumberOfGamesAsync()
        {
            return await Context.Games.CountAsync();
        }

        public Game Upsert(Game game)
        {
            Game returnGame = null;
            if (game.Id > 0)
            {
                returnGame = Context.Games.Find(game.Id);
                returnGame.Description = game.Description;
                returnGame.Name = game.Name;

                return returnGame;
            }
            else
            {
                Context.Games.Add(game);
                return game;
            }
        }

        public async Task UpsertAsync(Game game)
        {
            Game returnGame = null;
            if (game.Id > 0)
            {
                returnGame = await Context.Games.FindAsync(game.Id);
                returnGame.Description = game.Description;
                returnGame.Name = game.Name;

                return returnGame;
            }
            else
            {
                Context.Games.Add(game);
                return game;
            }
        }

        public async Task> GetAllWithCardDataAsync()
        {
            return await Context.Games
                .Include(g => g.GameSets)
                .Include(g => g.GameSets.Select(gs => gs.Cards))
                .ToArrayAsync();
        }

        public Task> Get(params int[] ids)
        {
            throw new NotImplementedException();
        }
    }
}

Модуль библиотеки документов (C#.NET) — 2020

using Sparcpoint.Storage;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace Sparcpoint.Inventory.Modules.Documents
{
    public class DefaultDocumentListService : IDocumentListService
    {
        private readonly IInstanceModelRepository _Documents;
        private readonly IFileStorageConnector _Connector;
        private readonly IUserContext _User;

        public DefaultDocumentListService(
            IInstanceModelRepository documents,
            IFileStorageConnector connector,
            IUserContext user
        )
        {
            _Documents = PreConditions.ParameterNotNull(documents, nameof(documents));
            _Connector = PreConditions.ParameterNotNull(connector, nameof(connector));
            _User = PreConditions.ParameterNotNull(user, nameof(user));
        }

        public async Task CreateListAsync(string name, string? description = null, CustomAttributes? attributes = null)
        {
            DocumentList list = new DocumentList
            {
                Name = name,
                Description = description ?? string.Empty,
                CustomAttributes = attributes ?? new CustomAttributes()
            };

            return await _Documents.Add(list);
        }

        public async Task FindAsync(int listId)
            => await _Documents.Find(listId);

        public async Task GetDownloadStreamAsync(Uri location)
        {
            if (!_Connector.CanHandle(location))
                throw new InvalidOperationException($"The controlling connector cannot handle the provided uri: '{location}'");

            IFile file = await _Connector.GetFileAsync(location);
            return await file.GetStreamAsync();
        }

        public async Task SaveDocumentAsync(int listId, string name, Stream data, string? mediaType = null)
        {
            DocumentList? list = await FindAsync(listId);
            PostConditions.Found(list, "Document List", listId);

            IDirectory directory = await _Connector.GetRootDirectoryAsync();
            IFile file = await directory.WriteFileAsync(name, data);

            DocumentList.Item document = new DocumentList.Item
            {
                Name = name,
                MediaType = mediaType ?? string.Empty,
                UploadUserId = _User.CurrentUserId,
                Location = file.Path
            };
            list.Documents = list.Documents.Concat(new[] { document }).ToArray();
            await _Documents.Update(listId, list);

            return file.Path;
        }
    }
}

© Habrahabr.ru