«Это просто мой стиль кода»

от автора

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

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

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

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

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

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

Честно говоря, меня бесит, когда я слышу, что разработчик использует одну из этих фраз в качестве оправдания корявости своего кода. Почему? Казалось бы, сущая мелочь. На самом деле, меня раздражает не сама фраза, а глубинный эгоизм, который в ней заключен. Есть только две ситуации, в которых вы вольны писать код так, как вам вздумается: вы пишете лично для себя, и никто больше вашу программу читать не будет ИЛИ речь идет об изолированной среде, например, 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<records> input) {   records r;   SqlConnection s;   SqlCommand cmd;   int index;   int index2;   int foundIndex;   var grp = new List<records>();   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<ProductRecord> 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<ProductRecord> records) {   ProductRecord[] groupedRecords = GroupProductRecordsByName(records);    using (var sqlConn = OpenSqlConnection())   using (var sqlCommand = BuildBulkProductCountCommand(sqlConn, groupedRecords))   {     sqlCommand.ExecuteNonQuery();   }   }  private ProductRecord[] GroupProductRecordsByName(IEnumerable<ProductRecord> 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<ProductRecord> 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<ProductRecord> 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<Game> 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<Game> 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<Game> GetAll()         {             return Context.Games.OrderBy(x => x.Name).ToArray();         }          public async Task<IEnumerable<Game>> GetAllAsync()         {             return await Context.Games.OrderBy(x => x.Name).ToArrayAsync();         }          public int GetNumberOfGames()         {             return Context.Games.Count();         }          public async Task<int> 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<Game> 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<IEnumerable<Game>> GetAllWithCardDataAsync()         {             return await Context.Games                 .Include(g => g.GameSets)                 .Include(g => g.GameSets.Select(gs => gs.Cards))                 .ToArrayAsync();         }          public Task<IEnumerable<Game>> 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<DocumentList> _Documents;         private readonly IFileStorageConnector _Connector;         private readonly IUserContext _User;          public DefaultDocumentListService(             IInstanceModelRepository<DocumentList> 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<int> 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<DocumentList?> FindAsync(int listId)             => await _Documents.Find(listId);          public async Task<Stream> 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<Uri> 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;         }     } } 


ссылка на оригинал статьи https://habr.com/ru/company/ispmanager/blog/680504/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *