Автор этой статьи размышляет о понятии так называемого «стиля программирования» и о том, насколько часто разработчики оправдывают им низкое качество своего кода. Приглашаем вас оценить приведенные автором примеры и поделиться собственным мнением в вопросах стиля!
Стиль кода. Я слышал эти слова, эту глупость в сотне разнообразных вариантов:
«Это просто мой стиль программирования».
«Все пишут код по-разному».
«Так я лучше всего понимаю код».
И так далее, и тому подобное...
Честно говоря, меня бесит, когда я слышу, что разработчик использует одну из этих фраз в качестве оправдания корявости своего кода. Почему? Казалось бы, сущая мелочь. На самом деле, меня раздражает не сама фраза, а глубинный эгоизм, который в ней заключен. Есть только две ситуации, в которых вы вольны писать код так, как вам вздумается: вы пишете лично для себя, и никто больше вашу программу читать не будет ИЛИ речь идет об изолированной среде, например, 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;
}
}
}
Комментарии (65)
ARad
02.08.2022 21:11+1Есть инструменты форматирования кода, они помогают.
F0iL
02.08.2022 22:16+7Форматирование - только лишь одна из составляющих чистого кода.
agim-gal
03.08.2022 02:39А ещё в ide есть функция выноса кода в отдельный метод (ну в vs точно есть). И вот еë применение ещё больше к чистому коду приближает. С ней есть удобная эвристика, если не получается сходу придумать имя, то или не тот кусок выделяешь, или исходный код никуда не годится.
vadimr
02.08.2022 21:15+14Хотел бы я, как автор статьи, жить в прекрасном мире, где по названию функции, написанной неизвестно кем, можно понять, что она делает, и дальше не читать код. К сожалению, реальные программы совсем другие.
А ещё важнее, что в реальную чужую программу мы обычно залезаем тогда, когда она работает не так, как задумывал автор. И тут красивыми названиями не отделаться.
SadOcean
03.08.2022 16:29Но ведь к этому нужно стремиться. Хотя бы чтобы часть функций была понятна.
vadimr
04.08.2022 08:37Это несомненно.
Но автор почему-то уверен, что, разбодяжив код на два экрана вместо одного, он увеличил его понятность. Вот это уже спорно.
Вообще, два экрана хуже одного, так как не видимы одновременно. Автор в ответ, вероятно, сказал бы, что это просто пример, иллюстрирующий в маленькой статье какие-то общие мысли. Но тогда мы возвращаемся к тому, оформление – в немалой степени дело вкуса и конкретных обстоятельств, а не какие-то железные правила. А также не надо забывать о том, что каждый из его отдельных геттеров-сеттеров (или что там было в статье) всё равно надо проверять при поиске ошибок.
Вообще, тут ещё надо разбираться, понятность для кого мы увеличиваем. У какого-нибудь гуру программирования в голове много паттернов, и ему понятен сложный код, а простой индусский крестьянин ничего сложнее присваиваний с одной операцией в правой части в линейной последовательности понимать не обучен, циклы уже с трудом. Среднее где-то посередине, но откуда нам известно, что нужно ориентироваться на среднее? Это всё не такие простые вопросы.
Racheengel
02.08.2022 21:15+5Быстрый код лучше медленного. Стабильный лучше глючного. Короткий лучше длинного. Понятный лучше умного.
Простое лучше сложного. KISS forever.
yeswell
03.08.2022 02:37+90) Работающий лучше неработающего
alexxxnf
03.08.2022 17:03+1Я много раз видел код, который "просто работает". К сожалению, чаще всего это его единственное достоинство. И в тот момент, когда такой код нужно "немного подправить", оказывается, что быстрее будет написать с нуля, чем разобраться.
А если бы код был не рабочим, но хорошо структурированным и читаемым, то подправить его мог бы любой разработчик за разумное время.
Поэтому мои приоритеты такие:
Читаемость.
Эффективность.
Красота.
dopusteam
04.08.2022 09:32А что за красота? А где приоритет, что код вообще работает? На первом месте всегда выполнение функционала должно быть, нет?
А если бы код был не рабочим, но хорошо структурированным и читаемым, то подправить его мог бы любой разработчик за разумное время.
Если он не работает, то его правка может быть равносильна полному переписыванию с нуля, как в первом примере. Выглядит как крайность
alexxxnf
04.08.2022 13:05А что за красота?
Красота - это такие вещи как лаконичность, семантический сахар. Они делают код эстетически более приятным, но не сильно влияют на читаемость или эффективность.
Если он не работает, то его правка может быть равносильна полному переписыванию с нуля, как в первом примере.
Представьте ситуацию, когда новую фичу начал имплементировать крутой разработчик, но в середине работы его чем-то отвлекли. Например, его навыки понадобились, чтобы срочно пофиксить баг, или его сбил автобус. Если код хорошо структурирован, то его может подхватить и дописать менее квалифицированный разработчик. А если крутой разраб будет писать в стиле "смотри как я умею" (привет однострочникам в 400 символов), то именно в этом случае придётся или потратить много времени, чтобы во всём разобраться, или потратить много времени, чтобы переписать с нуля.
Я считаю, что лучше сделать часть работы, которую потом можно доделать, чем всю работу, которую потом придётся переделывать. Именно моэтому функциональность не в приоритете.
А если разработчик может писать так, чтобы сразу было и читаемо, и работало, то это просто прекрасно.
dopusteam
04.08.2022 14:58Как часто у Вас разработчик не закончив фичу отдаёт её на доделку?
Менее квалицифированному разработчику имхо в сложном коде с идеально продуманными абстракциями будет сложнее разобраться. И запороть он сможет всё гораздо сильнее, т.е. нарушить где то такие то продуманные первым разрабом правила, а потом уже то всё поправить будет гораздо сложнее, чем если бы он в одном месте кучу кода написал.Я считаю, что лучше сделать часть работы, которую потом можно доделать, чем всю работу, которую потом придётся переделывать. Именно моэтому функциональность не в приоритете.
А я считаю, что лучше сделать, чтоб работало, чем, чтоб было круто и красиво, но не работало. https://wiki.c2.com/?MakeItWorkMakeItRightMakeItFast
А продумывать заранее миллион абстракций - ну не знаю.
Но заметьте, я не предлагаю писать плохо или в один метод всё запихнуть. Я за то, чтоб сначала код в принципе работал, но без оверинжиниринга. Чтоб увидеть реальные кейсы\проблемы, собрать те же логи, найти места для расширения.
alexxxnf
04.08.2022 13:05А что за красота?
Красота - это такие вещи как лаконичность, семантический сахар. Они делают код эстетически более приятным, но не сильно влияют на читаемость или эффективность.
Если он не работает, то его правка может быть равносильна полному переписыванию с нуля, как в первом примере.
Представьте ситуацию, когда новую фичу начал имплементировать крутой разработчик, но в середине работы его чем-то отвлекли. Например, его навыки понадобились, чтобы срочно пофиксить баг, или его сбил автобус. Если код хорошо структурирован, то его может подхватить и дописать менее квалифицированный разработчик. А если крутой разраб будет писать в стиле "смотри как я умею" (привет однострочникам в 400 символов), то именно в этом случае придётся или потратить много времени, чтобы во всём разобраться, или потратить много времени, чтобы переписать с нуля.
Я считаю, что лучше сделать часть работы, которую потом можно доделать, чем всю работу, которую потом придётся переделывать. Именно моэтому функциональность не в приоритете.
А если разработчик может писать так, чтобы сразу было и читаемо, и работало, то это просто прекрасно.
Jian
03.08.2022 06:15+1Простое лучше сложного.
Простейшая пузырьковая сортировка лучше быстрой и сложной? :)
Bakuard
03.08.2022 07:28+2Одновременно соблюсти все принципы KISS редко получается и тогда, вы выбираете каким из них отдать предпочтение.
Racheengel
03.08.2022 14:32Смотря для каких целей. Если речь об обучении первокурсников - то в этом контексте, возможно, да.
Но в общем случае порядок имеет значение, конечно. Можно использовать SPIFE - Stability, Performance, Intuitive, Functional, Extendable - именно в таком порядке.
Sergeant101
03.08.2022 16:06Ну это как посмотреть:
пузырьковая сортировка даёт среднюю сложность О(n^2), а быстрая - О(n log(n)), так что "быстрая сортировка" проще "сортировки пузырьком" )))
omxela
02.08.2022 21:34+6Господи Боже, от одного взгляда на этот пример у меня болит мозг…
Вот тут-то и проблема, имхо. Есть пишущий (код), а есть читающий. И если читающему неуютно, то он начинает обвинять пишущего в неуважении и эгоизме. Насколько вообще уместны этические оценки в, казалось бы, технических делах - автору виднее. Выход очевиден. Если речь идёт о производстве, то устанавливаются соответствующие стандарты, люди договариваются на берегу, и дальше обсуждают только очевидно вредящие делу нарушения. А то все побросают работу и будут на основе коротких фрагментов кода обвинять друг друга в эгоизме и бог ещё знает каких смертных грехах.
kuznetsovkd
02.08.2022 21:54+3Такое ощущение что вам никогда не приходилось читать свой грязный код. В первую очередь нужно писать чистый код для себя. Если у вас фриланс без поддержки то там да можно навалить кучу и гори оно всё.
F0iL
02.08.2022 22:17+1Нередко бывает, что неуютно читать оказывается даже для самого написавшего через N лет.
Ellinist
03.08.2022 10:38+1Тут дело в том, что мы не стоим на месте, а меняемся вместе со временем. И то, что нам очевидно сейчас, будет совсем неочевидно через пару лет. Вот отсюда и эта неуютность.
Вот у меня есть кусок кода, который я вымучивал почти месяц - там сложные математические расчеты - три цикла друг в друге с кучей ветвлений по условиям. Так вот я, вернувшись к нему через полгода, не смог понять практически ничего (кода рабочий, кстати), хотя параллельно с написанием кода я задокументировал алгоритм (даже с картинками).
Мне кажется, дело здесь не в красоте или некрасивости - дело как раз в пресловутом "я художник, я так вижу". Сегодня художник видит так, а через год - по другому. Вот и вся недолга.
nmrulin
02.08.2022 22:23+4Ну вообще , если поручить поддерживать код условному джуну Васе , то вполне вероятно, он будет лучше всего именно первый "плохой" поддерживать по причине того, что всё в нём поймёт.
Или взять задачу , что надо код из одного языка быстро конвертировать в другой.
Но вообще лично у меня ситуация не раз была, что я считал свой код хорошим, но смотря на него через год, менял мнение.
Nikoobraz
03.08.2022 09:46+1Джун Вася будет его лучше поддерживать только потому, что не додумается, что трудность в чтении этого кода - это не проблема его неопытности, а это сам код плох. Так что Вася будет грызть этот кактус пока и в самом деле не начнет с ним справляться, но такими темпами сгорит весьма быстро.
nmrulin
03.08.2022 10:25Ну с первым вариантом кода она будет гораздо быстрее справляться. Т.к. сравните количество знаний операторов и функций, которые нужны для поддержки первого кода и второго. При том что важно также и твёрдое знание, а не "один раз видел где-то". Другое дело, что если он будет править только первый код, так джуном и останется, а в другое место его и стажёром не возьмут.
alexxxnf
03.08.2022 17:08А может, если джун Вася всё понял в плохом коде, то код не так уж и плох?
Код из статьи я за разумное время не понял.
kpmy
02.08.2022 23:00+1Я понимаю инженеров-строителей-архитекторов, у них гравитация, сопромат, роза ветров и плавание почвы. Материальные основания для строгих правил.
Внутри компьютера ограничений гораздо меньше, эти ограничения известны. И в статье нет каких-то новых материальных оснований для ограничений. Был бы автор, а не переводчик, можно было бы спросить "обоснуй объективно".
Материальным будет разве что голод, который наступит если бизнес будет настолько критичен к code-style, чтобы при прочих равных за него увольнять. Но это уже совсем другая история.lair
03.08.2022 02:43-2Был бы автор, а не переводчик, можно было бы спросить "обоснуй объективно".
А зачем?
kpmy
03.08.2022 15:34Ну вдруг у него был инсайт и он что-то такое понял. Сомнительно, но всё же, спросить стоило бы.
А так да, понятно что очередной идейно намагниченный видит мир только так, и никак иначе.lair
03.08.2022 15:37Так вроде бы написано прямо во вступлении:
It is a pet peeve of mine
Проблема, однако, немного шире. Вот вы поняли, что нет объективного обоснования для того или иного code style (не новость, прямо скажем)… а дальше что?
kpmy
03.08.2022 20:09-1Тут всё как всегда, возможно лично меня это никогда не коснётся и я до пенсии доживу не потратив ни калории на удовлетворение чьего-то чувства "читабельности" или "красивости". Но учитывая, какую идейную заряженность имеет каждый такой тредик, думаю, мне просто повезло не столкнуться с подобным.
Code-style это ведь часть айсберга. Есть другие подобные конвенциональные, необоснованные явления, что давят на человечка посильнее, чем душный ревьюер. Да и ревьюера можно понять, если он сам понимает, а вот если не понимает, то такого понять нельзя и принять тоже. Впрочем, это другое!
В общем, коллективное осознание массовойшизофрениисубъективности это путь из царства необходимости в царство свободы.lair
03.08.2022 20:12Но учитывая, какую идейную заряженность имеет каждый такой тредик
А какую, кстати?
В общем, коллективное осознание массовой шизофрении субъективности это путь из царства необходимости в царство свободы.
Каким же образом?
Если вернуться к практике: вот у нас есть команда разработчиков, человек так в 50. Должен быть в ней code style — который, как мы помним, нельзя объективно обосновать — или же надо его отвергуть, как "конвенциональное, необоснованное явление"?
moonster
03.08.2022 00:03+2Если программист не умеет читать "по диагонали" - то и написать код, пригодный для чтения "по диагонали", он сможет разве что случайно, как бы он не уважал коллег.
Неоднократно наблюдал, как после доработки "профессионалом" код из семантически наполненного письма другу превращался в набор инструкций для машины.
Лучше просто не допускать к коду людей, которые не умеют читать.
Сначала плавать научитесь, потом бассейн наполним.
Wesha
03.08.2022 00:57-3Правило двух секунд: "если на то, чтобы понять, что делает строчка кода, вам понадобилось более двух секунд — она должна быть переписана".
0xd34df00d
03.08.2022 01:14+16Это, очевидно, зависит от выразительности языка и от вашего знакомства с предметной областью, и в общем случае это не так.
Wesha
04.08.2022 02:34+1На самом деле всё элементарно. Достаточно а) давать переменным и методам названия, объясняющие суть происходящено — например,
worksheet_line_number
вместоn
иcount_positive_numbers()
вместоpos_cnt()
— и b) не бояться разбивать одну сложную для понимания строку на несколько простых.0xd34df00d
04.08.2022 06:24Ну ок. Давайте сделаем это здесь: тыц. Что получится?
Wesha
04.08.2022 06:33+1Мне этот язык не знаком, поэтому никаких конкретных советов по конкретно этому коду дать не могу. Был бы знаком — дал бы, но забесплатно разбираться не готов.
invasy
03.08.2022 16:25Переписать, чтобы появилось более одной непонятной строчки?
И про предметную область верно замечено выше. Бизнес-логики там всякие, абстракции. И всё может поместиться в одну строку в итоге.
Bronx
03.08.2022 01:05+7for(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();
Во-первых, у вас будет выход за границы массива при коллекции нулевой длины.
Во-вторых,string.Join(",", ...)
гораздо короче и понятнее, чемfor(...) { }
, и позволяет не повторять одинаковый кусок кода (или проверять индекс, чтобы решить, где ставить разделитель). Под капотом тот жеStringBuilder
.
В-третьих,AddParametersAndGenerateValueRow()
берёт на себя две обязанности: генерит строку и генерит сайд-эффект, нарушая SRP. Что закономерно отразилось в ужасном названии функции.
MKMatriX
03.08.2022 09:17+3Выскажу очень еретическую мысль.
Отсутствие общего хорошего стиля имеет свои преимущества.
Даже если мы говорим о разных способах табуляции. Даже если мы говорим об откровенной лапше и говнокоде. И даже если все это легко правится автофиксами и проверяется линтерами. И даже в большой команде. Конечно я не говорю о бесполезности приведения кода к нормальному и общему стилю, но все же, очередной раз делая ревью, не стоит править весь авторский стиль.
- Итак про плюсы. Представьте себе файлик с кучей разных стилей, говнокодом, разными отступами, по разному названными переменными и так далее. По началу он конечно раздражает количеством энтропии. Однако при поиске бага, глаза сами тут же полезут в места с неправильными, не логичными отступами. Что характерно, программисты, которые не однообразно ставят отступы, допускают больше ошибок. Т.е. хороший стиль, он как дезодорант, добавляет коду хороший запах скрывая содержимое. Или, переформулирую, хороший программист, зачастую, пишет код с четким стилем, но программист с четким стилем, не обаятельно хороший. Ну и конечно хороший стиль особо не помогает развиваться, конечно, думая о коде для его форматирования, мы все же развиваемся, но лучше просто писать код.
- Второй плюс заключается в узнавании авторов. Конечно их можно посмотреть через гит или аналоги, но все же через стиль это проще. И зачастую зная авторов, вы узнаете и типичных их ошибки, точнее их стиль мышления и можете предположить к каким ошибкам он ведет. Свыше того четко виден их уровень, типа написать crud — может, но с проверкой безопасности затупит.
- Третий плюс в экономии времени. Действительно код в одном стиле читать проще, только одинаковое написание кода не ведет к одинаковому мышлению, поэтому все равно, при переходе от одного автора к другому, в чтении происходит заминка. А вот на привод к одному стилю бывает что тратится до 90% времени. И ладно если у вас настроены линтеры, автофиксы, и все это проверяется прямо перед пушем. Но ведь бывает стилизация через ревью или очень редкие правила. Да еще и ревью идет не сразу, и зачастую с первого раза не высказываются все претензии, или комментарий в стиле "вот тут ошибка А подправьте ее во всем реквесте". Тогда больше одного двух реквестов в день ждать не стоит. И весьма большую часть времени разработчик будет тратить на борьбу с прокрастинацией, ибо править стиль ему банально не интересно.
- Еще странный плюс, это количество ошибок. Этот плюс конечно сомнительный, ведь когда мы правим стиль, мы можем найти ошибки, также после правок стиля порой наши ошибки найти легче, плюс на местах стыковки с чужим говнокодом ошибок станет больше, но все же. Тратя время не на работу над стилем, и держа код похожим на наш образ мышления, мы совершаем меньше ошибок и проще их находим.
Резюмируя.
Уменьшение количества стилевых правил может дать дополнительную информацию о коде и значительно сократить время на его написание, также уменьшает количество ошибок и облегчает правки, если речь о куске кода одного автора.И я нисколько не против общего стиля, я даже за, просто наболело. Но все же пример с письмами у автора поста не корректный, автор исходил из тезиса, что эти письма в любом случае напишут и мысль в них будет одинаковая, вне зависимости от подчерка. Это не так. Вероятней их содержимое сократилось бы, а отношение авторов писем стало бы более негативным. Это нормально. Мы люди, и когда нас заставляют делать что-то не приятное мы работаем хуже. Также как программисты, мы обычно стремимся у уменьшению энтропии, но наши возможности ограниченны. А фанатичное отношение к чему-то, даже к хорошему коду, при возведении в абсолют, приносит весьма большое количество вреда, и даже зачастую больше чем пользы.
aamonster
03.08.2022 09:29Как-то долго и сложно объясняется... Есть ведь простая красивая формулировка: "пиши код так, будто поддерживать его будет психопат, знающий, где ты живёшь".
faultedChip
03.08.2022 09:33+6Для меня третий вариант выглядит не многим лучше первого: он точно также зашумлён, просто вместо того чтобы всё делать вручную и в одном методе, ударились в другую крайность и побили код на кучу мелких методов. Чтобы понять что и как работает в итоге всё равно придётся их все просмотреть.
Причём (почти) все эти методы специализированные, не подразумевающие повторного использования. Зачем, например, выделять добавление параметров в метод AddParametersAndGenerateValueRow? Ладно бы там была сложная логика, но это буквально размазывание ответственности по методам.
Вобщем, на мой взгляд, второй вариант лучший из представленных (хотя и не идеальный). В идеале, наверно, стоило бы разделить логику на два метода - подготовка данных и их сохранения. И второй метод можно спокойно вынести в репозиторий, если используется этот шаблон проектирования (что заодно позволяет немного абстрагироваться от базы данных).
panzerfaust
03.08.2022 09:43+4После прочтения уже десятков статей на хабре на эту тему я понимаю, что статьи не могут поменять ровно ничего. Лучший способ распространить свое видение - это делом показывать, что оно лучше. Если в ваших проектах любой баг обнаруживается за минуты, любая доработка безболезненно влезает в рамки спринта и не приносит регресса, любой джун въезжает в процесс за неделю - то есть смысл делиться этой фактурой с коллегами, мягко продвигать свой подход. Если все действительно классно, то коллеги проникнуться и понесут знание в свои проекты и другие команды.
IvanSTV
03.08.2022 12:53+2Любой код пишется в неодинаковых обстоятельствах и решает неодинаковые задачи. Потому стиль постоянно разный при всем желании сделать все по уму. К тому же код – он живой. И стиль в начале может не соответствовать стилю в конце, если нет времени и возможности его довылизывать.
Вот, например, два месяца назад мне поставили задачу сделать очередной оптимальный подбор и запихнуть результат в систему. Решено, оформлено и откомментировано. А потом на него начали наслаиваться проверки и подпроверки (оптому что пользователь вносит коррективы в процессы, к которым код), выяснилось, например, что типов упаковки не три, как предполагалось изначально, а больше, и они вообще должны быть настраиваемые. А еще через несколько недель выяснилось, что для формы вывода надо вообще типы упаковок другие, потому что то, что заложено в системе, не всегда соответствует физическому виду, и по физике типы склад составляет вообще сам, посмотрев глазками. Формы вывода надо был делить по признаку, который надо было еще распознать общим анализом полученных результатов массива. Да еще чертова масса требований.
Весь стиль посыпался как карточный домик. То, на что хватало массива, стало необходимым делать через классы и коллекции, обращения к другим программам и обработки, которые были в одном общем цикле, надо было выносить в отдельные функции, так как их стала куча дублирующих. В результате я сказал, что все перепишу набело с нуля. Но это был только один процесс.
А был бы большой проект с кучей тесно связанных модулей – я бы заново ничего не переписал, а продолжил бы так, как оно есть, накручивать и накручивать модули, вставлять куски, дублировать элементы кода и пр., то есть, просто ничего не осталось бы, кроме как продолжаь писать грязный код.
Причем хороший стиль и удобство чтения не всегда связаны. Один китаец написал код в 40 строчек и 26 подпрограмм по три-четыре строчки каждая. Вроде бы по уму – каждой заполняемой ячейке декларации свой код, который можно модульно поменять при изменении условий расчета (корпорация международная). А реально-замучаешься колесико крутить. Я даже распечатал декларацию и подписал ячейке ручкой имена подпрограмм чтобы понимать, куда лезть, пока разбирался.
khe404
03.08.2022 13:33+1Будучи дилетантом, который пишет программы в большинстве случаев для своего собственного образования я с интересом прочел Вашу статью. (Кстати в пример ужасного кода похоже закралась опечатка: класс называется record а используется он как records. И не инициализируется mdate_time которая далее в таком виде и попадает в базу данных. Думаю в виде начала эпохи unix )
У меня создалось впечатление, что разница стиля между Примером 1 и Примером 2 не принципиальная и стилистическая, а скорее относящаяся к использованию конкретно возможностей языка C#.
Несомненно, автор дал более "человеческое" название основному методу RunProc и оптимизировал множественные запросы к базе данных.
Но основным отличием стало то, что кусок кода с двумя вложенными циклами подсчета суммы по ключу был заменен на
var grouped = input.GroupBy(r => r.Name) .Select(g => new { Timestamp = g.First().Timestamp, Name = g.Key, Quantity = g.Sum(e => e.Quantity) }).ToArray();
Таким образом, тот же самый вложенный цикл должен быть реализован где то в другом месте. Ну или даже какой-то более сложный аналог с O(ln(n)).
При этом переход от Примера 2 к Примеру 3, на мой взгляд добавил еще больше специфики именно C# однако является хорошим примером не столько объектно-ориентированного, сколько процедурного программирования.
На самом деле мне всегда казалось что существует некое правило - каждая независимая задача должна решаться с использованием своего собственного метода ( процедуры, функции )) И тогда код будет легче читаться. Т.е. задача программиста в каждом процессе выделить подзадачи, грамотно их именовать и реализовать алгоритм.
Но, как и всегда должен быть баланс. И вот в этом балансе - делить ли задачу дальше или укрупнять и есть искусство написания кода.
Жаль, но я в это не умею. При этом продираться через деревья вызовов функций тоже не всегда просто. Видимо все же комментарии о том что делает данная процедура, это самое лучшее средство разобраться в будущем.
YChebotaev
Не совсем понятно где работает автор.
Ну, да, чистый код лучше не чистого. Но надо понимать что люди не компьютеры и не просто исполняют заданные программы, а постоянно решают задачи, в условиях к которым включены и собственные эмоции.
Эго - это как раз и есть взрослая часть личности, которая находит компромисс между своими интересами и правилами. Так что если чел настоящий эгоист, за него можно только порадоваться.
vagon333
Вынужден с вами не согласиться.
Профессионал это и профессиональная этика и стиль кода.
У меня могут быть какие угодно эмоции, но если через год я не могу разобраться в собственном коде, а другие разработчики избегают мой код - никакой эгоизм не в оправдание. Это просто непрофессионализм.
И наоборот - иногда встречяю код, который приятно читать, учусь и подбираю для себя массу полезного.
Спустя 15-20 лет в разработке я отказался от проявления эго.
Городость за качественную работу - это да, но эго и гордыни нет.
lgorSL
Не существует единственного правильного стиля кода. Общепринятые стили очень сильно различаются между языками, конкретными технологиями и областями и даже отдельными проектами, они могут быть диаметрально противоположными.
Есть какой-то набор идей, как написать более красивый и понятный код, но местами они лежат на грани между объективностью и предпочтениями.
Из примеров:
В C# любят писать геттеры-сеттеры на всё (даже в примере автора они зачем-то есть, хотя ничего не делают), в Python стараются обходиться без них.
В некоторых языках делают приватным всё подряд, в некоторых делают данные публичными и неизменяемыми, а рядышком люди пишут код на Python, в котором ни приватности, ни неизменяемости - и мир не рушится.
В Java под Android из-за неудачного решения одного мужика из Google названия полей классов m_camelCase, а в такой же java здорового человека просто используется camelCase.
Есть функциональные языки типа хаскелля, в них тот же самый код будет совсем иначе выглядеть.
Иногда люди накладывают сознательные ограничения на язык программирования - например, не используют исключения в С++ или goto.
Я вот смотрю на примеры автора на С# и меня прям коробят эти циклы for(int i; i < groups.length - 1; ++i), но это мои личные предпочтения.
А ещё код не существует сам по себе, он обычно существует в контексте какого-то проекта с каким-то устоявшимся стилем, и приходится подстраиваться под него.
KvanTTT
Это не совсем предпочтение — как правило свойства сериализуемые, а поля — нет.
Ну правилам инкапсуляции правильно делать все по-умолчанию приватным и неизменяемым, а уже потом открывать или делать изменяемым при необходимости. Все-таки это тоже не совсем субъективно — удобней ориентироваться в иммутабельном коде, в нем сложнее допустить ошибку, да он так и чище выглядит.
dopusteam
Но не везде это нужно, как минимум, и это вряд ли основная причина. Ну или, как минимум, не единственная
olsowolso
Я на плюсах пишу поля классов в виде m_camelCase, мне это кажется удобным - при чтении кода видно где поле класса, а где локальная переменная. В подобном наименовании есть какая-то порочная практика в принципе, или вопрос именно в разнице между стилями Java под Android и просто Java?
lgorSL
Я говорю именно про java под android и просто java. Язык один и тот же, программисты примерно тоже, но пишут по-разному. Причина: один из первых разработчиков Android когда-то решил, что он чаще читает код в браузере, а не в IDE с подсветкой, и типа так удобнее: https://habr.com/ru/post/333596/?ysclid=l6dcku117u402263940
Java разрешает в классе иметь методы и поля с одинаковыми именами. В С++ так вроде бы нельзя, там префикс m_ спасает от коллизий имён.
В любом случае и в С++ и в java можно писать и так и так, это скорее вопрос предпочтений.
YChebotaev
Ну так это тоже ваши эмоции ) Более взрослые, да. В итоге, суть вопроса сводится к банальному "конфликту поколений".
Ну, не знаю. Лично я считаю, что с менее компетентными коллегами стоит, наоборот, найти общий язык, и через эту связь попытаться донести ценности, чем окукливаются в своем коконе. Но это чисто мое мнение. Конечно, они все равно возьмут только нужное, но хотя-бы показать весь ассортимент, мне кажется - стоит того.