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

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

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

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

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

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

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


  1. YChebotaev
    02.08.2022 20:47
    +8

    Не совсем понятно где работает автор.

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

    Эго - это как раз и есть взрослая часть личности, которая находит компромисс между своими интересами и правилами. Так что если чел настоящий эгоист, за него можно только порадоваться.


    1. vagon333
      02.08.2022 21:28
      +12

      Вынужден с вами не согласиться.
      Профессионал это и профессиональная этика и стиль кода.

      У меня могут быть какие угодно эмоции, но если через год я не могу разобраться в собственном коде, а другие разработчики избегают мой код - никакой эгоизм не в оправдание. Это просто непрофессионализм.
      И наоборот - иногда встречяю код, который приятно читать, учусь и подбираю для себя массу полезного.

      Спустя 15-20 лет в разработке я отказался от проявления эго.
      Городость за качественную работу - это да, но эго и гордыни нет.


      1. lgorSL
        02.08.2022 22:54
        +20

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

        Есть какой-то набор идей, как написать более красивый и понятный код, но местами они лежат на грани между объективностью и предпочтениями.

        Из примеров:

        • В C# любят писать геттеры-сеттеры на всё (даже в примере автора они зачем-то есть, хотя ничего не делают), в Python стараются обходиться без них.

        • В некоторых языках делают приватным всё подряд, в некоторых делают данные публичными и неизменяемыми, а рядышком люди пишут код на Python, в котором ни приватности, ни неизменяемости - и мир не рушится.

        • В Java под Android из-за неудачного решения одного мужика из Google названия полей классов m_camelCase, а в такой же java здорового человека просто используется camelCase.

        • Есть функциональные языки типа хаскелля, в них тот же самый код будет совсем иначе выглядеть.

        • Иногда люди накладывают сознательные ограничения на язык программирования - например, не используют исключения в С++ или goto.

        Я вот смотрю на примеры автора на С# и меня прям коробят эти циклы for(int i; i < groups.length - 1; ++i), но это мои личные предпочтения.

        А ещё код не существует сам по себе, он обычно существует в контексте какого-то проекта с каким-то устоявшимся стилем, и приходится подстраиваться под него.


        1. KvanTTT
          03.08.2022 02:08
          +2

          В C# любят писать геттеры-сеттеры на всё (даже в примере автора они зачем-то есть, хотя ничего не делают),

          Это не совсем предпочтение — как правило свойства сериализуемые, а поля — нет.


          В некоторых языках делают приватным всё подряд, в некоторых делают данные публичными и неизменяемыми, а рядышком люди пишут код на Python, в котором ни приватности, ни неизменяемости — и мир не рушится.

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


          1. dopusteam
            03.08.2022 08:08
            +4

            Это не совсем предпочтение — как правило свойства сериализуемые, а поля — ненетн

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


        1. olsowolso
          03.08.2022 11:25

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


          1. lgorSL
            03.08.2022 11:44

            Я говорю именно про java под android и просто java. Язык один и тот же, программисты примерно тоже, но пишут по-разному. Причина: один из первых разработчиков Android когда-то решил, что он чаще читает код в браузере, а не в IDE с подсветкой, и типа так удобнее: https://habr.com/ru/post/333596/?ysclid=l6dcku117u402263940

            Java разрешает в классе иметь методы и поля с одинаковыми именами. В С++ так вроде бы нельзя, там префикс m_ спасает от коллизий имён.

            В любом случае и в С++ и в java можно писать и так и так, это скорее вопрос предпочтений.


      1. YChebotaev
        02.08.2022 23:59

        Ну так это тоже ваши эмоции ) Более взрослые, да. В итоге, суть вопроса сводится к банальному "конфликту поколений".

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


  1. ARad
    02.08.2022 21:11
    +1

    Есть инструменты форматирования кода, они помогают.


    1. F0iL
      02.08.2022 22:16
      +7

      Форматирование - только лишь одна из составляющих чистого кода.


      1. agim-gal
        03.08.2022 02:39

        А ещё в ide есть функция выноса кода в отдельный метод (ну в vs точно есть). И вот еë применение ещё больше к чистому коду приближает. С ней есть удобная эвристика, если не получается сходу придумать имя, то или не тот кусок выделяешь, или исходный код никуда не годится.


  1. vadimr
    02.08.2022 21:15
    +14

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

    А ещё важнее, что в реальную чужую программу мы обычно залезаем тогда, когда она работает не так, как задумывал автор. И тут красивыми названиями не отделаться.


    1. Hardcoin
      02.08.2022 21:35
      +1

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


      1. vadimr
        02.08.2022 21:37
        +1

        Да, это как частный случай.


    1. SadOcean
      03.08.2022 16:29

      Но ведь к этому нужно стремиться. Хотя бы чтобы часть функций была понятна.


      1. vadimr
        04.08.2022 08:37

        Это несомненно.

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

        Вообще, два экрана хуже одного, так как не видимы одновременно. Автор в ответ, вероятно, сказал бы, что это просто пример, иллюстрирующий в маленькой статье какие-то общие мысли. Но тогда мы возвращаемся к тому, оформление – в немалой степени дело вкуса и конкретных обстоятельств, а не какие-то железные правила. А также не надо забывать о том, что каждый из его отдельных геттеров-сеттеров (или что там было в статье) всё равно надо проверять при поиске ошибок.

        Вообще, тут ещё надо разбираться, понятность для кого мы увеличиваем. У какого-нибудь гуру программирования в голове много паттернов, и ему понятен сложный код, а простой индусский крестьянин ничего сложнее присваиваний с одной операцией в правой части в линейной последовательности понимать не обучен, циклы уже с трудом. Среднее где-то посередине, но откуда нам известно, что нужно ориентироваться на среднее? Это всё не такие простые вопросы.


        1. SadOcean
          05.08.2022 14:00

          Кароче, как и везде, важна твоя ЦА)
          Думай о том, для кого пишешь)


  1. Racheengel
    02.08.2022 21:15
    +5

    Быстрый код лучше медленного. Стабильный лучше глючного. Короткий лучше длинного. Понятный лучше умного.

    Простое лучше сложного. KISS forever.


    1. yeswell
      03.08.2022 02:37
      +9

      0) Работающий лучше неработающего


      1. Lexicon
        03.08.2022 11:52
        +1

        -1) Поэтому он пишет такой код, который нужен, чтобы не стать неработающим


      1. alexxxnf
        03.08.2022 17:03
        +1

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

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

        Поэтому мои приоритеты такие:

        1. Читаемость.

        2. Эффективность.

        3. Красота.


        1. dopusteam
          04.08.2022 09:32

          А что за красота? А где приоритет, что код вообще работает? На первом месте всегда выполнение функционала должно быть, нет?

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

          Если он не работает, то его правка может быть равносильна полному переписыванию с нуля, как в первом примере. Выглядит как крайность


          1. alexxxnf
            04.08.2022 13:05

            А что за красота?

            Красота - это такие вещи как лаконичность, семантический сахар. Они делают код эстетически более приятным, но не сильно влияют на читаемость или эффективность.

            Если он не работает, то его правка может быть равносильна полному переписыванию с нуля, как в первом примере.

            Представьте ситуацию, когда новую фичу начал имплементировать крутой разработчик, но в середине работы его чем-то отвлекли. Например, его навыки понадобились, чтобы срочно пофиксить баг, или его сбил автобус. Если код хорошо структурирован, то его может подхватить и дописать менее квалифицированный разработчик. А если крутой разраб будет писать в стиле "смотри как я умею" (привет однострочникам в 400 символов), то именно в этом случае придётся или потратить много времени, чтобы во всём разобраться, или потратить много времени, чтобы переписать с нуля.

            Я считаю, что лучше сделать часть работы, которую потом можно доделать, чем всю работу, которую потом придётся переделывать. Именно моэтому функциональность не в приоритете.

            А если разработчик может писать так, чтобы сразу было и читаемо, и работало, то это просто прекрасно.


            1. dopusteam
              04.08.2022 14:58

              Как часто у Вас разработчик не закончив фичу отдаёт её на доделку?
              Менее квалицифированному разработчику имхо в сложном коде с идеально продуманными абстракциями будет сложнее разобраться. И запороть он сможет всё гораздо сильнее, т.е. нарушить где то такие то продуманные первым разрабом правила, а потом уже то всё поправить будет гораздо сложнее, чем если бы он в одном месте кучу кода написал.

              Я считаю, что лучше сделать часть работы, которую потом можно доделать, чем всю работу, которую потом придётся переделывать. Именно моэтому функциональность не в приоритете.

              А я считаю, что лучше сделать, чтоб работало, чем, чтоб было круто и красиво, но не работало. https://wiki.c2.com/?MakeItWorkMakeItRightMakeItFast

              А продумывать заранее миллион абстракций - ну не знаю.

              Но заметьте, я не предлагаю писать плохо или в один метод всё запихнуть. Я за то, чтоб сначала код в принципе работал, но без оверинжиниринга. Чтоб увидеть реальные кейсы\проблемы, собрать те же логи, найти места для расширения.


          1. alexxxnf
            04.08.2022 13:05

            А что за красота?

            Красота - это такие вещи как лаконичность, семантический сахар. Они делают код эстетически более приятным, но не сильно влияют на читаемость или эффективность.

            Если он не работает, то его правка может быть равносильна полному переписыванию с нуля, как в первом примере.

            Представьте ситуацию, когда новую фичу начал имплементировать крутой разработчик, но в середине работы его чем-то отвлекли. Например, его навыки понадобились, чтобы срочно пофиксить баг, или его сбил автобус. Если код хорошо структурирован, то его может подхватить и дописать менее квалифицированный разработчик. А если крутой разраб будет писать в стиле "смотри как я умею" (привет однострочникам в 400 символов), то именно в этом случае придётся или потратить много времени, чтобы во всём разобраться, или потратить много времени, чтобы переписать с нуля.

            Я считаю, что лучше сделать часть работы, которую потом можно доделать, чем всю работу, которую потом придётся переделывать. Именно моэтому функциональность не в приоритете.

            А если разработчик может писать так, чтобы сразу было и читаемо, и работало, то это просто прекрасно.


    1. Jian
      03.08.2022 06:15
      +1

      Простое лучше сложного.

      Простейшая пузырьковая сортировка лучше быстрой и сложной? :)


      1. Bakuard
        03.08.2022 07:28
        +2

        Одновременно соблюсти все принципы KISS редко получается и тогда, вы выбираете каким из них отдать предпочтение.


      1. Racheengel
        03.08.2022 14:32

        Смотря для каких целей. Если речь об обучении первокурсников - то в этом контексте, возможно, да.

        Но в общем случае порядок имеет значение, конечно. Можно использовать SPIFE - Stability, Performance, Intuitive, Functional, Extendable - именно в таком порядке.


      1. Sergeant101
        03.08.2022 16:06

        Ну это как посмотреть:

        пузырьковая сортировка даёт среднюю сложность О(n^2), а быстрая - О(n log(n)), так что "быстрая сортировка" проще "сортировки пузырьком" )))


    1. Groramar
      03.08.2022 07:31
      +2

      Явное лучше неявного :)


      1. Bronx
        03.08.2022 10:23
        +6

        Отсутствующий код лучше наличествующего :)


  1. dmitryvolochaev
    02.08.2022 21:28
    +1

    Я художник, я так вижу


  1. omxela
    02.08.2022 21:34
    +6

    Господи Боже, от одного взгляда на этот пример у меня болит мозг…

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


    1. kuznetsovkd
      02.08.2022 21:54
      +3

      Такое ощущение что вам никогда не приходилось читать свой грязный код. В первую очередь нужно писать чистый код для себя. Если у вас фриланс без поддержки то там да можно навалить кучу и гори оно всё.


    1. F0iL
      02.08.2022 22:17
      +1

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


      1. Ellinist
        03.08.2022 10:38
        +1

        Тут дело в том, что мы не стоим на месте, а меняемся вместе со временем. И то, что нам очевидно сейчас, будет совсем неочевидно через пару лет. Вот отсюда и эта неуютность.

        Вот у меня есть кусок кода, который я вымучивал почти месяц - там сложные математические расчеты - три цикла друг в друге с кучей ветвлений по условиям. Так вот я, вернувшись к нему через полгода, не смог понять практически ничего (кода рабочий, кстати), хотя параллельно с написанием кода я задокументировал алгоритм (даже с картинками).

        Мне кажется, дело здесь не в красоте или некрасивости - дело как раз в пресловутом "я художник, я так вижу". Сегодня художник видит так, а через год - по другому. Вот и вся недолга.


  1. nmrulin
    02.08.2022 22:23
    +4

    Ну вообще , если поручить поддерживать код условному джуну Васе , то вполне вероятно, он будет лучше всего именно первый "плохой" поддерживать по причине того, что всё в нём поймёт.

    Или взять задачу , что надо код из одного языка быстро конвертировать в другой.

    Но вообще лично у меня ситуация не раз была, что я считал свой код хорошим, но смотря на него через год, менял мнение.


    1. Nikoobraz
      03.08.2022 09:46
      +1

      Джун Вася будет его лучше поддерживать только потому, что не додумается, что трудность в чтении этого кода - это не проблема его неопытности, а это сам код плох. Так что Вася будет грызть этот кактус пока и в самом деле не начнет с ним справляться, но такими темпами сгорит весьма быстро.


      1. nmrulin
        03.08.2022 10:25

        Ну с первым вариантом кода она будет гораздо быстрее справляться. Т.к. сравните количество знаний операторов и функций, которые нужны для поддержки первого кода и второго. При том что важно также и твёрдое знание, а не "один раз видел где-то". Другое дело, что если он будет править только первый код, так джуном и останется, а в другое место его и стажёром не возьмут.


    1. alexxxnf
      03.08.2022 17:08

      А может, если джун Вася всё понял в плохом коде, то код не так уж и плох?

      Код из статьи я за разумное время не понял.


  1. kpmy
    02.08.2022 23:00
    +1

    Я понимаю инженеров-строителей-архитекторов, у них гравитация, сопромат, роза ветров и плавание почвы. Материальные основания для строгих правил.

    Внутри компьютера ограничений гораздо меньше, эти ограничения известны. И в статье нет каких-то новых материальных оснований для ограничений. Был бы автор, а не переводчик, можно было бы спросить "обоснуй объективно".

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


    1. lair
      03.08.2022 02:43
      -2

      Был бы автор, а не переводчик, можно было бы спросить "обоснуй объективно".

      А зачем?


      1. kpmy
        03.08.2022 15:34

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

        А так да, понятно что очередной идейно намагниченный видит мир только так, и никак иначе.


        1. lair
          03.08.2022 15:37

          Так вроде бы написано прямо во вступлении:


          It is a pet peeve of mine

          Проблема, однако, немного шире. Вот вы поняли, что нет объективного обоснования для того или иного code style (не новость, прямо скажем)… а дальше что?


          1. kpmy
            03.08.2022 20:09
            -1

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

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

            В общем, коллективное осознание массовой шизофрении субъективности это путь из царства необходимости в царство свободы.


            1. lair
              03.08.2022 20:12

              Но учитывая, какую идейную заряженность имеет каждый такой тредик

              А какую, кстати?


              В общем, коллективное осознание массовой шизофрении субъективности это путь из царства необходимости в царство свободы.

              Каким же образом?


              Если вернуться к практике: вот у нас есть команда разработчиков, человек так в 50. Должен быть в ней code style — который, как мы помним, нельзя объективно обосновать — или же надо его отвергуть, как "конвенциональное, необоснованное явление"?


  1. moonster
    03.08.2022 00:03
    +2

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

    Неоднократно наблюдал, как после доработки "профессионалом" код из семантически наполненного письма другу превращался в набор инструкций для машины.

    Лучше просто не допускать к коду людей, которые не умеют читать. Сначала плавать научитесь, потом бассейн наполним.


    1. Tim777
      04.08.2022 08:27

      Пишем для друга или для машины?


      1. moonster
        04.08.2022 12:45

        Конечно для друга. Писать для машины можно только одноразовый код.


  1. Wesha
    03.08.2022 00:57
    -3

    Правило двух секунд: "если на то, чтобы понять, что делает строчка кода, вам понадобилось более двух секунд — она должна быть переписана".


    1. 0xd34df00d
      03.08.2022 01:14
      +16

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


      1. Wesha
        04.08.2022 02:34
        +1

        На самом деле всё элементарно. Достаточно а) давать переменным и методам названия, объясняющие суть происходящено — например, worksheet_line_number вместо n и count_positive_numbers() вместо pos_cnt()— и b) не бояться разбивать одну сложную для понимания строку на несколько простых.


        1. 0xd34df00d
          04.08.2022 06:24

          Ну ок. Давайте сделаем это здесь: тыц. Что получится?


          1. Wesha
            04.08.2022 06:33
            +1

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


    1. invasy
      03.08.2022 16:25

      Переписать, чтобы появилось более одной непонятной строчки?

      И про предметную область верно замечено выше. Бизнес-логики там всякие, абстракции. И всё может поместиться в одну строку в итоге.


  1. Bronx
    03.08.2022 01:05
    +7

    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();
    


    Во-первых, у вас будет выход за границы массива при коллекции нулевой длины.

    Во-вторых, string.Join(",", ...) гораздо короче и понятнее, чем for(...) { }, и позволяет не повторять одинаковый кусок кода (или проверять индекс, чтобы решить, где ставить разделитель). Под капотом тот же StringBuilder.

    В-третьих, AddParametersAndGenerateValueRow() берёт на себя две обязанности: генерит строку и генерит сайд-эффект, нарушая SRP. Что закономерно отразилось в ужасном названии функции.


  1. Alex-ok
    03.08.2022 08:27
    -1

    Уважаемый автор, ваш пример №3 на чистый код не тянет. Там нет ни одного комментария. Это неуважение к команде.


    1. Jian
      03.08.2022 09:21
      +4

      Бывает стиль кода писать без комментариев (с чем я не согласен), который следует концепции "хороший код - понятен без комментариев".


  1. MKMatriX
    03.08.2022 09:17
    +3

    Выскажу очень еретическую мысль.


    Отсутствие общего хорошего стиля имеет свои преимущества.


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


    • Итак про плюсы. Представьте себе файлик с кучей разных стилей, говнокодом, разными отступами, по разному названными переменными и так далее. По началу он конечно раздражает количеством энтропии. Однако при поиске бага, глаза сами тут же полезут в места с неправильными, не логичными отступами. Что характерно, программисты, которые не однообразно ставят отступы, допускают больше ошибок. Т.е. хороший стиль, он как дезодорант, добавляет коду хороший запах скрывая содержимое. Или, переформулирую, хороший программист, зачастую, пишет код с четким стилем, но программист с четким стилем, не обаятельно хороший. Ну и конечно хороший стиль особо не помогает развиваться, конечно, думая о коде для его форматирования, мы все же развиваемся, но лучше просто писать код.
    • Второй плюс заключается в узнавании авторов. Конечно их можно посмотреть через гит или аналоги, но все же через стиль это проще. И зачастую зная авторов, вы узнаете и типичных их ошибки, точнее их стиль мышления и можете предположить к каким ошибкам он ведет. Свыше того четко виден их уровень, типа написать crud — может, но с проверкой безопасности затупит.
    • Третий плюс в экономии времени. Действительно код в одном стиле читать проще, только одинаковое написание кода не ведет к одинаковому мышлению, поэтому все равно, при переходе от одного автора к другому, в чтении происходит заминка. А вот на привод к одному стилю бывает что тратится до 90% времени. И ладно если у вас настроены линтеры, автофиксы, и все это проверяется прямо перед пушем. Но ведь бывает стилизация через ревью или очень редкие правила. Да еще и ревью идет не сразу, и зачастую с первого раза не высказываются все претензии, или комментарий в стиле "вот тут ошибка А подправьте ее во всем реквесте". Тогда больше одного двух реквестов в день ждать не стоит. И весьма большую часть времени разработчик будет тратить на борьбу с прокрастинацией, ибо править стиль ему банально не интересно.
    • Еще странный плюс, это количество ошибок. Этот плюс конечно сомнительный, ведь когда мы правим стиль, мы можем найти ошибки, также после правок стиля порой наши ошибки найти легче, плюс на местах стыковки с чужим говнокодом ошибок станет больше, но все же. Тратя время не на работу над стилем, и держа код похожим на наш образ мышления, мы совершаем меньше ошибок и проще их находим.

    Резюмируя.
    Уменьшение количества стилевых правил может дать дополнительную информацию о коде и значительно сократить время на его написание, также уменьшает количество ошибок и облегчает правки, если речь о куске кода одного автора.


    И я нисколько не против общего стиля, я даже за, просто наболело. Но все же пример с письмами у автора поста не корректный, автор исходил из тезиса, что эти письма в любом случае напишут и мысль в них будет одинаковая, вне зависимости от подчерка. Это не так. Вероятней их содержимое сократилось бы, а отношение авторов писем стало бы более негативным. Это нормально. Мы люди, и когда нас заставляют делать что-то не приятное мы работаем хуже. Также как программисты, мы обычно стремимся у уменьшению энтропии, но наши возможности ограниченны. А фанатичное отношение к чему-то, даже к хорошему коду, при возведении в абсолют, приносит весьма большое количество вреда, и даже зачастую больше чем пользы.


    1. moonster
      03.08.2022 10:35
      +3

      Очень тонко. Завидую. Как такому можно научиться? )


  1. aamonster
    03.08.2022 09:29

    Как-то долго и сложно объясняется... Есть ведь простая красивая формулировка: "пиши код так, будто поддерживать его будет психопат, знающий, где ты живёшь".


  1. faultedChip
    03.08.2022 09:33
    +6

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

    Причём (почти) все эти методы специализированные, не подразумевающие повторного использования. Зачем, например, выделять добавление параметров в метод AddParametersAndGenerateValueRow? Ладно бы там была сложная логика, но это буквально размазывание ответственности по методам.

    Вобщем, на мой взгляд, второй вариант лучший из представленных (хотя и не идеальный). В идеале, наверно, стоило бы разделить логику на два метода - подготовка данных и их сохранения. И второй метод можно спокойно вынести в репозиторий, если используется этот шаблон проектирования (что заодно позволяет немного абстрагироваться от базы данных).


  1. panzerfaust
    03.08.2022 09:43
    +4

    После прочтения уже десятков статей на хабре на эту тему я понимаю, что статьи не могут поменять ровно ничего. Лучший способ распространить свое видение - это делом показывать, что оно лучше. Если в ваших проектах любой баг обнаруживается за минуты, любая доработка безболезненно влезает в рамки спринта и не приносит регресса, любой джун въезжает в процесс за неделю - то есть смысл делиться этой фактурой с коллегами, мягко продвигать свой подход. Если все действительно классно, то коллеги проникнуться и понесут знание в свои проекты и другие команды.


  1. IvanSTV
    03.08.2022 12:53
    +2

    Любой код пишется в неодинаковых обстоятельствах и решает неодинаковые задачи. Потому стиль постоянно разный при всем желании сделать все по уму. К тому же код – он живой. И стиль в начале может не соответствовать стилю в конце, если нет времени и возможности его довылизывать.

    Вот, например, два месяца назад мне поставили задачу сделать очередной оптимальный подбор и запихнуть результат в систему. Решено, оформлено и откомментировано. А потом на него начали наслаиваться проверки и подпроверки (оптому что пользователь вносит коррективы в процессы, к которым код), выяснилось, например, что типов  упаковки не три, как предполагалось изначально, а больше, и они вообще должны быть настраиваемые. А еще через несколько недель выяснилось, что для формы вывода надо вообще типы упаковок другие, потому что то, что заложено в системе, не всегда соответствует физическому виду, и по физике типы склад составляет вообще сам, посмотрев глазками. Формы вывода надо был делить по признаку, который надо было еще распознать общим анализом полученных результатов массива. Да еще чертова масса требований.

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

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

    Причем хороший стиль и удобство чтения не всегда связаны. Один китаец написал код в 40 строчек и 26 подпрограмм по три-четыре строчки каждая. Вроде бы по уму – каждой заполняемой ячейке декларации свой код, который можно модульно поменять при изменении условий расчета (корпорация международная). А реально-замучаешься колесико крутить. Я даже распечатал декларацию и подписал ячейке ручкой имена подпрограмм чтобы понимать, куда лезть, пока разбирался.


  1. 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# однако является хорошим примером не столько объектно-ориентированного, сколько процедурного программирования.

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

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

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