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

Стиль кода. Я слышал эти слова, эту глупость в сотне разнообразных вариантов:
«Это просто мой стиль программирования».
«Все пишут код по-разному».
«Так я лучше всего понимаю код».
И так далее, и тому подобное...
Честно говоря, меня бесит, когда я слышу, что разработчик использует одну из этих фраз в качестве оправдания корявости своего кода. Почему? Казалось бы, сущая мелочь. На самом деле, меня раздражает не сама фраза, а глубинный эгоизм, который в ней заключен. Есть только две ситуации, в которых вы вольны писать код так, как вам вздумается: вы пишете лично для себя, и никто больше вашу программу читать не будет ИЛИ речь идет об изолированной среде, например, 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; } } }
