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