«Энергетический» троян BlackEnergy внедряется через уязвимость в Microsoft Office 2013


Фото: csoonline

Специалисты по информационной безопасности из SentinelOne обнаружили новую тактику распространения malware вредоносного по BlackEnergy, атакующего SCADA-системы по всей Европе. Последняя версия этого ПО распространяется вместе с Microsoft Office, а расчет делается на невнимательных и неосторожных работников энергокомпаний, которые и приносят зловред.

Последняя версия malware носит название BlackEnergy 3, и это то же ПО, что использовалось для атаки на энергетические системы Украины. Команда специалистов из SentinelOne провела реверс-инжиниринг malware и обнаружила признаки того, что это ПО распространяется способом, описанным выше.

BlackEnergy 3 использует уязвимость Office 2013, которая была исправлена некоторое время назад, поэтому он может сработать только на машинах, где нет патча, или где работник компании открывает зараженный Excel-документ.

Вероятность того, что в энергетических компаниях используется устаревшее ПО невелика, поэтому основным «источником» проникновения вредоноса на предприятие являются все же его сотрудники — вольно или невольно.

«Сейчас используется уязвимость CVE-2014-4114, в OLE упаковщике 2 (packager.dll). При этом каждый исполняемый файл создан с использованием компиляторов разных версий, что позволяет нам говорить о вовлечении различных групп в это кампанию — примерно то же, что и в случае R&D проекта, в котором работает несколько команд. И в готовом ПО есть несколько уникальных отпечатков каждой из групп», — говорится в отчете исследователей.

Вывод следующий: BlackEnergy уже работает во многих украинских системах, а также в энергосистемах европейских стран. Если это действительно так, malware сможет быть использовано для создания блекаутов и прочих проблемных ситуаций, в самое неожиданное время.

Полный отчет по проблеме доступен здесь.

ссылка на оригинал статьи https://habrahabr.ru/post/276257/

Как программирование позволяет логично выражать мысли

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

Этим вопросом в одной из своих статей задается один из основателей MIT Media Lab Марвин Минский. Он пытается развеять миф о том, что программа – лишь набор строгих правил и инструкций. Минский пишет: «Это ложное убеждение возникает из-за того, что люди путают форму с содержанием […] Разработчик должен четко следовать синтаксису выбранного языка, но содержание, которое он хочет через него выразить, ничем не ограничивается».

Программа STUDENT, разработанная сотрудником исследовательского центра Пало-Альто Дэниелем Боброу (Daniel Bobrow) еще в 1964 году, решала школьные задачки по алгебре в таком виде:

Маша была в два раза старше Ани, когда Маше было столько лет, сколько сейчас Ане. Если Маше сейчас 24, то сколько лет Ане?

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

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

Если вы пишите программу на Fortran и хотите вызвать уже имеющуюся процедуру, вам нужно воспользоваться одной из строго фиксированных команд, например, GO TO. Вы не можете заменить ее другой командой, но вы можете перейти с ее помощью в любое место программы, то есть у вас есть определенная свобода действий.

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

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

Трактор, велосипед и автомобиль Tesla – транспортные средства, но их используют для разных целей. То же и с языками. Ruby и JavaScript идеально подходят для создания сайтов, Java и C++ часто используют для создания торговых алгоритмов, Python и R отлично справляются со статистическими задачами и обработкой информации.

Языки часто выбирают на основе их удобства, безопасности и скорости – как и транспорт – в соответствии с текущей задачей. Поэтому обычно мы выбираем язык, который больше нам подходит. Некоторые разработчики выбирают Ruby за его гибкость, другие предпочитают строгость Java. Но иногда встречаются те, кто не любит объектно-ориентированное программирование: эти люди не используют его преимуществ просто потому, что они больше работали с методами процедурных языков.

Таким образом, особенности языка и в самом деле ограничивают наше мышление. Однако мы сами можем преодолеть эти ограничения. Например, в Lisp есть несколько необычных выражений («cons», «sexp», «car», «cdr»), не имеющих аналогов в других языках. И все же Lisp дает нам свободу выражения своих идей в рамках этого языка.

В книге Design Patterns хорошо показано, как нужно выражать мысли в C++. Больше половины паттернов из книги отсутствует в Lisp, так как их можно выразить в нем, не меняя структуры языка. Выходит, что языки программирования формируют ход наших мыслей, причем каждый по-своему.

Чтобы в этом убедиться, достаточно написать одну и ту же программу на разных языках и посмотреть, какие они выдадут результаты. Затем перевести программу с одного языка на другой. При том, что выбирать следует значительно отличающиеся друг от друга языки, например, те же Lisp и C++. В итоге вы поймете, что язык выражает не все ваши мысли, но самое главное – это то, что он позволяет оценить, что реализовать легко, а что – сложно.

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

Продолжая тему Python, можно вспомнить написанный на нем открытый проект Django. В 2003 году новостное агентство World Online решило заняться разработкой веб-фреймворка, который бы экономил время работы разработчиков. Два года спустя проект стал открытым. Python хорош тем, что он несложный, а главное – он позволяет разработчику быстро создать рабочий прототип.

Поэтому им часто пользуются в стартапах, а его популярность за последние несколько лет выросла до небес. Среди других продуктов на Python выделяются BitTorrent, MyPait, MoinMoin и другие. Этот язык можно дополнить Javascript, который помогает презентовать свои идеи. В итоге связка Python + Javascript идеально подойдет для реализации и представления любых бизнес-идей, а также их масштабирования.

Бывший сотрудник Microsoft Майк Болодзин в интервью Business Insider рассказывает о важности умения выражать свои мысли. Программисты должны уметь грамотно выражать даже самые обыкновенные идеи в письменной форме (помимо программирования).

Это поможет не только эффективнее общаться с руководством, но и предъявлять права на результаты своей работы. Болодзин вспоминает, как однажды не признали его вклад в работу: «Я спорил, что это моя идея, и знал, что этого не могли не заметить». Более ясная презентация своих идей позволит вам избежать подобных ситуаций.

Кроме того, если вы пишете хорошее программное обеспечение, вам, наверняка, придется больше общаться с другими людьми. По мнению Болодзина, если вы показываете хороший результат, то вам придется писать на родном языке столько, сколько вы обычно пишите на Java или Objective C. Для этого достаточно выработать привычку регулярно писать: это не сложнее, чем вести свой блог.

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

ссылка на оригинал статьи https://habrahabr.ru/post/276253/

Визуализация concurrency в Go с WebGL

Одной из самых сильных сторон языка программирования Go является встроенная поддержка concurrency, основанная на труде Тони Хоара «Communicating Sequential Processes». Go создан для удобной работы с многопоточным программированием и позволяет очень легко строить довольно сложные concurrent-программы. Но задумывались ли вы когда-нибудь, как выглядят различные паттерны concurrency визуально?

Конечно, задумывались. Все мы, так или иначе, мыслим визуальными образами. Если я попрошу вас о чём-то, что включает числа «от 1 до 100», вы мгновенно их «увидите» в своей голове в той или иной форме, вероятно даже не отдавая себе в этом отчёт. Я, к примеру, ряд от 1 до 100 вижу как линия с числами уходящая от меня, поворачивающая на 90 градусов вправо на числе 20 и продолжающая до 1000+. И, покопавшись в памяти, я вспоминаю, что в самом первом детском саду в раздевалке вдоль стены были написаны номерки, и число 20 было как-раз в углу. У вас же, вероятно, какое-то свое представление. Или вот, другой частый пример — представьте круглый год и 4 сезона года — кто-то их видит как квадрат, каждая грань которого принадлежит сезону, кто-то — как круг, кто-то ещё как-то.

Так или иначе, позвольте мне показать мою попытку визуализировать основные паттерны concurrency с помощью Go и WebGL. Эти интерактивные визуализации более-менее отражают то, как я вижу это в своей голове. Интересно будет услышать, насколько это отличается от визуализаций читателей.


Итак, давайте начнем с простейшего примера — «Hello, concurrent world», чтобы познакомиться с концепцией моего подхода.

Привет, concurrent мир

package main  func main() {     // создаем новый канал типа int     ch := make(chan int)      // запускаем новую анонимную горутину     go func() {         // отправляем 42 в канал         ch <- 42     }()     // ждем, читаем из канала     <-ch }


Ссылка на интерактивное WebGL демо
Здесь синие линии представляют горутины, время «бежит» вниз по оси Y. Тонкие синие линии соединяющие ‘main’ и ‘go #19’ — это отметки начала и завершения жизни горутины, показывающие предков и детей. Красные стрелочки демонстрируют событие отправки сообщения в канал, и отправленное значение подписано. На самом деле, «отправка в канал» и «чтение из канала» это два отдельных события, и поведение будет сильно отличаться между буферизированными и небуферизированными каналами, но я два этих события анимирую как одно — «передача значения по каналу». Строка "#19" в названии анонимной горутины — это реальный ID запущенной горутины. Хотя официально узнать ID горутин и нельзя (чтобы люди не городили другие модели concurrency, в которых идентификаторы играют важную роль), но для вот таких хакерских случаев таки можно — об этом хорошо написано в статье Скота Мэнсфилда «Goroutine IDs».

Таймеры

Фактически, наш простейший Hello, world выше может использоваться для создания простейшего таймера. В стандартной библиотеке Go есть такие удобные функции, как time.After или time.Tick, но давайте реализуем наш собственный — напишем функцию, которая создает канал, запускает горутину, которая спит необходимое время и пишет в канал, и возвратим этот канал вызывающему.

package main  import "time"  func timer(d time.Duration) <-chan int {     c := make(chan int)     go func() {         time.Sleep(d)         c <- 1     }()     return c }  func main() {     for i := 0; i < 24; i++ {         c := timer(1 * time.Second)         <-c     } }


Ссылка на интерактивное WebGL демо
Здорово, правда? Но идём дальше.

Пинг-понг

Этот интересный пример concurrency был взят из известного доклада гуглера Sameer Ajmani "Advanced Concurrency Patterns". Конечно, этот пример не слишком advanced, но для тех, кто только знакомится с concurrency в Go он должен быть интересным и демонстративным.

Итак, у нас есть канал table, выполняющий роль стола, есть мяч Ball, который является переменной типа int и хранит в себе количество ударов по нему, и есть горутины-игроки, которые «забирают мяч со стола» (читают из канала), «бьют по нему» (увеличивают переменную) и «бросают обратно на стол» (пишут в канал).

package main  import "time"  func main() {     var Ball int     table := make(chan int)     go player(table)     go player(table)      table <- Ball     time.Sleep(1 * time.Second)     <-table }  func player(table chan int) {     for {         ball := <-table         ball++         time.Sleep(100 * time.Millisecond)         table <- ball     } }


Ссылка на интерактивное WebGL демо
На этом моменте я хочу ещё раз обратить ваше внимание на ссылку с интерактивным WebGL демо, доступную под каждой анимацией — открыв в новом табе, вы можете двигать, вращать, увеличивать/уменьшать и рассматривать эти 3D анимации, как вам угодно, а так же замедлять/ускорять и перезапускать их.

Теперь, давайте запустим три игрока-горутины, вместо двух:

    go player(table)     go player(table)     go player(table)


Ссылка на интерактивное WebGL демо
В данном примере мы видим, что каждый игрок забирает мяч со стола по-очереди, и вы можете задаться вопросом — почему именно так, кто гарантирует этот порядок?

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

for i := 0; i < 100; i++ {     go player(table) }


Ссылка на интерактивное WebGL демо
Порядок FIFO теперь более очевиден, не так-ли? Мы можем запросто запустить и миллион горутин (они дешевые, и это ок в больших Go программах иметь сотни тысяч горутин), но для наших целей это будет слишком много. Давайте перейдем к другим паттернам.

Fan-in

Один из самых известных паттернов это так называемый fan-in паттерн. Он является противоположностью паттерну fan-out, который мы рассмотрим далее. Если вкратце, то fan-in — это функция, читающая из нескольких источников и мультиплексирующая всё в один канал.
К примеру:

package main  import (     "fmt"     "time" )  func producer(ch chan int, d time.Duration) {     var i int     for {         ch <- i         i++         time.Sleep(d)     } }  func reader(out chan int) {     for x := range out {         fmt.Println(x)     } }  func main() {     ch := make(chan int)     out := make(chan int)     go producer(ch, 100*time.Millisecond)     go producer(ch, 250*time.Millisecond)     go reader(out)     for i := range ch {         out <- i     } }


Ссылка на интерактивное WebGL демо
Как мы видим, первый producer генерирует числа каждые 100мс, второй — каждые 250мс, а reader получает числа от обоих продюсеров сразу же. Мультиплексирование, по-сути, происходит в функции main.

Fan-out

Противоположностью fan-in является fan-out или workers паттерн. Множество горутин читают из одного канала, забирая на обработку какие-то данные и эффективно распределяя работу между ядрами CPU. Отсюда и название "workers". В Go реализовывать этот паттерн очень просто — запустите пачку горутин, передав канал через параметр и пишите в этот канал ваши данные, а мультиплексирование и распределение будет происходит автоматически благодаря рантайму Go.

package main  import (     "fmt"     "sync"     "time" )  func worker(tasksCh <-chan int, wg *sync.WaitGroup) {     defer wg.Done()     for {         task, ok := <-tasksCh         if !ok {             return         }         d := time.Duration(task) * time.Millisecond         time.Sleep(d)         fmt.Println("processing task", task)     } }  func pool(wg *sync.WaitGroup, workers, tasks int) {     tasksCh := make(chan int)      for i := 0; i < workers; i++ {         go worker(tasksCh, wg)     }      for i := 0; i < tasks; i++ {         tasksCh <- i     }      close(tasksCh) }  func main() {     var wg sync.WaitGroup     wg.Add(36)     go pool(&wg, 36, 50)     wg.Wait() }


Ссылка на интерактивное WebGL демо
Одна вещь, на которую тут хотелось бы обратить внимание — параллелизм. Лего заметить, что горутины-воркеры бегут параллельно, забирая себе «работу» по каналам, одна за другой. По данной анимации можно также увидеть, что горутины делают это почти одновременно. К сожалению, пока что в анимации не видно, где горутина реально работает, а где блокируется, и также тут временной масштаб уже близок к порогу погрешности ошибки, но конкретно эта анимация была записана на программе, бегущей на 4 ядрах, тоесть с GOMAXPROCS=4. Чуть дальше мы рассмотрим этот вопрос подробнее.

А пока что, давайте попробуем что-то посложнее — воркеры, у которых есть свои, саб-воркеры.

package main  import (     "fmt"     "sync"     "time" )  const (     WORKERS    = 5     SUBWORKERS = 3     TASKS      = 20     SUBTASKS   = 10 )  func subworker(subtasks chan int) {     for {         task, ok := <-subtasks         if !ok {             return         }         time.Sleep(time.Duration(task) * time.Millisecond)         fmt.Println(task)     } }  func worker(tasks <-chan int, wg *sync.WaitGroup) {     defer wg.Done()     for {         task, ok := <-tasks         if !ok {             return         }          subtasks := make(chan int)         for i := 0; i < SUBWORKERS; i++ {             go subworker(subtasks)         }         for i := 0; i < SUBTASKS; i++ {             task1 := task * i             subtasks <- task1         }         close(subtasks)     } }  func main() {     var wg sync.WaitGroup     wg.Add(WORKERS)     tasks := make(chan int)      for i := 0; i < WORKERS; i++ {         go worker(tasks, &wg)     }      for i := 0; i < TASKS; i++ {         tasks <- i     }      close(tasks)     wg.Wait() }


Ссылка на интерактивное WebGL демо
Здорово. Конечно, можно было сделать больше и воркеров, и саб-воркеров, но я стремился сделать анимацию максимально наглядной.

Есть гораздо более сложные паттерны, воркеров с сабворкерами со своими сабворкерами, и каналы, которые сами передаются по каналам, но идея fan-out должна быть понятна.

Серверы

Следующий популярный паттерн, похожий на fan-out, это серверы. Он отличается тем, что горутины стартуют динамически, выполняют необходимую работу и завершаются. И довольно часто этот паттерн применяется для реализации серверов — слушаем порт, принимаем соединение, стартуем горутину, которая дальше займется входящим запросом, передав ей соединение, а в это время слушаем дальше, ожидая следующее соединение. Это достаточно удобно и позволяет реализовать эффективный сервер, выдерживающий 10K соединений, очень просто. Вгляните на следующий пример:

package main  import "net"  func handler(c net.Conn) {     c.Write([]byte("ok"))     c.Close() }  func main() {     l, err := net.Listen("tcp", ":5000")     if err != nil {         panic(err)     }     for {         c, err := l.Accept()         if err != nil {             continue         }         go handler(c)     } }


Ссылка на интерактивное WebGL демо
Этот пример не слишком интересен — по-сути, тут ничего особо не происходит. Хотя, конечно, под капотом там заботливо спрятана громадная сложность и алгоритмы. «Простота сложна».

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

package main  import (     "fmt"     "net"     "time" )  func handler(c net.Conn, ch chan string) {     ch <- c.RemoteAddr().String()     c.Write([]byte("ok"))     c.Close() }  func logger(ch chan string) {     for {         fmt.Println(<-ch)     } }  func server(l net.Listener, ch chan string) {     for {         c, err := l.Accept()         if err != nil {             continue         }         go handler(c, ch)     } }  func main() {     l, err := net.Listen("tcp", ":5000")     if err != nil {         panic(err)     }     ch := make(chan string)     go logger(ch)     go server(l, ch)     time.Sleep(10 * time.Second) }


Ссылка на интерактивное WebGL демо
Достаточно демонстративно, не так ли? На этой анимации видно, что наш логгер может быстро стать узким местом, если количество соединений будет расти, а логгер будет не слишком быстрым (скажем, он будет сереализовать данные и куда-то еще отправлять). Но мы можем решить это, использовав уже знакомый нам паттерн fan-out. Давайте напишем это.

Сервер+Воркер

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

package main  import (     "net"     "time" )  func handler(c net.Conn, ch chan string) {     addr := c.RemoteAddr().String()     ch <- addr     time.Sleep(100 * time.Millisecond)     c.Write([]byte("ok"))     c.Close() }  func logger(wch chan int, results chan int) {     for {         data := <-wch         data++         results <- data     } }  func parse(results chan int) {     for {         <-results     } }  func pool(ch chan string, n int) {     wch := make(chan int)     results := make(chan int)     for i := 0; i < n; i++ {         go logger(wch, results)     }     go parse(results)     for {         addr := <-ch         l := len(addr)         wch <- l     } }  func server(l net.Listener, ch chan string) {     for {         c, err := l.Accept()         if err != nil {             continue         }         go handler(c, ch)     } }  func main() {     l, err := net.Listen("tcp", ":5000")     if err != nil {         panic(err)     }     ch := make(chan string)     go pool(ch, 4)     go server(l, ch)     time.Sleep(10 * time.Second) }


Ссылка на интерактивное WebGL демо
Мы улучшили наш сервер, эффективно распределив задачу для логгера между 4-мя горутинами, но всё равно видим, что логгер таки может стать узким местом. Тысячи соединений сходятся в одном канале. перед тем как мультиплексироваться между горутинами. Но, конечно, это случится уже при гораздо больших нагрузках, чем в предыдущем варианте.

Решето Эратосфена

Но довольно fan-in/fan-out экспериментов. Давайте посмотрим на более интересные пример. Один из моих любимых — это «Решето Эратосфена» на горутинах и каналах, найденное в докладе "Go Concurrency Patterns". Решето Эратосфена это древний алгоритм нахождения простых чисел до заданного лимита. Его суть заключается в последовательном вычеркивании всех чисел, делящихся на каждое последующее найденное простое число. Алгоритм «в лоб» не слишком эффективен, особенно на мультиядерных машинах.

Вариант реализации этого алгоритма с горутинами и каналами запускает по одной горутине на каждое найденное простое число, и эта горутина фильтрует числа, которые на него делятся. Когда же найдено первое простое число в горутине — оно отправляется в главную горутину(main) и выводится на экран. Этот алгоритм тоже далеко не самый эффективный, но я нахожу его потрясающе элегантным. Вот сам код:

// A concurrent prime sieve package main  import "fmt"  // Send the sequence 2, 3, 4, ... to channel 'ch'. func Generate(ch chan<- int) {     for i := 2; ; i++ {         ch <- i // Send 'i' to channel 'ch'.     } }  // Copy the values from channel 'in' to channel 'out', // removing those divisible by 'prime'. func Filter(in <-chan int, out chan<- int, prime int) {     for {         i := <-in // Receive value from 'in'.         if i%prime != 0 {             out <- i // Send 'i' to 'out'.         }     } }  // The prime sieve: Daisy-chain Filter processes. func main() {     ch := make(chan int) // Create a new channel.     go Generate(ch)      // Launch Generate goroutine.     for i := 0; i < 10; i++ {         prime := <-ch         fmt.Println(prime)         ch1 := make(chan int)         go Filter(ch, ch1, prime)         ch = ch1     } }

А теперь взгляните на анимацию.

Ссылка на интерактивное WebGL демо
Не забудьте покрутить его интерактивно в 3D пространстве по ссылке выше. Мне очень нравится, как иллюстративен этот пример и изучение его в 3D может помочь понять сам алгоритм лучше. Мы видим, что первая горутина(generate) посылает первое простое число (2) в main, затем стартует первая горутина-фильтр, отсеивающая двойки, затем тройки, пятерки, семерки… и каждый раз новое найденное простое число отправляется в main — это особенно хорошо видно при виде сверху. Красивый алгоритм, в том числе и в 3D.

GOMAXPROCS

Теперь давайте вернёмся к нашему примеру с воркерами. Помните, я написал, что этот пример был запущен с GOMAXPROCS=4? Это потому что все эти анимации не нарисованы, это реальные трейсы реальных программ.

Для начала, давайте освежим память и вспомним, что такое GOMAXPROCS:

GOMAXPROCS устанавливает максимальное количество ядер CPU, которые могут исполнять код одновременно

Я изменил код воркеров слегка, чтобы они делали реальную работу. нагружая процессор, а не просто спали. Затем запустил код без каких либо изменений на Linux-машине с двумя процессорами по 12 ядер каждый — сначала с GOMAXPROCS=1, затем с GOMAXPROCS=24.

Итак. первая анимация показывает одну и ту же программу, бегущую на 1-м ядре, вторая — на 24-х ядрах.

WebGL анимация 1 WebGL анимация 24
Скорость анимации времени разная в этих примерах (я хотел, чтобы все анимации занимали фиксированное время по высоте), но разница должна быть очевидна. При GOMAXPROCS=1 следующий воркер забирает работу (читает из канала) только когда освободилось ядро процессора и предыдущая горутина отработала свою порцию. С 24-мя ядрами, горутины почти сразу же разбирают задачи и ускорение огромно.

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

Утечки горутин

Что ещё мы можем продемонстрировать из мира concurrency? Одна из вещей, которая мне приходит в голову, это утечки горутин. Они могут случаться по неосторожности, или, скажем, если вы запустили горутину, но она вышла из области видимости.

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

Посмотрите:

Ссылка на интерактивное WebGL демо
Мне больно даже смотреть на это 🙂 Каждая линия — это потраченные ресурсы компьютера и бомба с часовым механизмом для вашей программы.

Concurrency is not Parallelism

Слово concurrency часто переводят как «параллелизм», но это не совсем верно. По правде, хорошего перевода я не знаю, поэтому везде тут и пишу без перевода. Но сама тема, объясняющая отличия между concurrency и параллелизмом раскрыта многократно, в том числе и Робом Пайком в замечательном одноимённом докладе. Посмотрите, если ещё не.

Если вкратце, то:

Параллелизм — это просто много штук, запущенных параллельно.
Concurrency — это способ структурировать программу.

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

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

Итак, вот это параллелизм. Просто много штук, бегущих параллельно.

Ссылка на интерактивное WebGL демо

И вот это параллелизм. Ещё больше параллельных штук, что, впрочем, ничего не меняет.

Ссылка на интерактивное WebGL демо

Но вот это — concurrency:

И вот это:

И вот это тоже concurrency:

Как это было сделано?

Чтобы создать эти анимации, я написал две программы — gotracer и gothree.js. Первая делает следующие вещи:

  • парсит AST-дерево исходного кода примеров на Go (ещё один плюс простой грамматики Go) и вставляет специальный вывод на событиях, относящихся к concurrency — старт/стоп горутины, запись/чтения в канал, создание канала
  • запускает модифицированную программу
  • анализирует вывод, и генерирует специальный JSON с ивентами и таймштампами

Пример результирующего JSON-а:

Далее, gothree.js использует мощь шикарной библиотеки Three.js, чтобы рисовать и анимировать эти данные в 3D с помощью WebGL. Небольшой враппер, чтобы втиснуть это в одну демо-страничку и готово.

Впрочем, этот подход очень ограничен. Мне приходилось очень аккуратно подбирать примеры, переименовывать каналы, чтобы получать корректный трейс. С этим подходом нет легкого способа связать каналы между горутинами, если они называются внутри функции иначе. Также есть проблемы с таймингом — вывод в консоль занимает порой больше времени, чем запуск горутины, запись в канал и выход, поэтому в некоторых примерах мне приходилось чуть подтюнивать, вставляя time.Sleep (в примере с таймерами анимация чуть некорректна поэтому).

Вобщем-то. это основная причина, по которой я пока-что не открываю код. Сейчас я играюсь ещё с execution tracer-ом Дмитрия Вьюкова — он, похоже, даёт нужный уровень детализации, хотя не содержит информации о том, что именно было отправлено в канал. Возможно есть ещё какие-то способы достичь максимально детального трейса, буду исследовать дальше. Пишите мне в твиттер или тут в комментариях, если есть идеи. Было бы круто, чтобы этот инструмент в итоге перерос в 3D concurrency-дебаггер, применимый к абсолютно любой программе на Go, без исключений.

Эта статья изначально была в виде доклада на Go Meetup-е во Львове (23 января 2016), затем опубликована на английском языке в моём блоге. Также на HackerNews и на Reddit.

ссылка на оригинал статьи https://habrahabr.ru/post/276255/

Качество гарантировано: Как в действительности выглядит работа тестировщика игр (Часть 2)

Первая часть здесь.

Старый видеролик с рекламой колледжа Уэствуд стал чем-то вроде шутки в мире видеоигр. Два парня, с комфортом устроившись на диване, убивают в хлам контроллеры, увлеченно играя на Sony PlayStation. Входит девушка и говорит: «Эй, ребята, вы уже закончили тестировать эту игру? У меня тут еще одна». «Мы только прошли третий уровень, графику надо немного подтянуть», — отвечает один из парней. Затем, развернувшись к своему другу, он улыбается, как будто только что выиграл в лотерею: «Не могу поверить, что мы играем в игры, и нам еще за это платят». «Знаю, – отвечает ему второй. – И моя мама говорила, что это мое увлечение видеоиграми ни к чему хорошему не приведет».

Именно так на протяжение долгого времени люди представляли себе жизнь тех, кто занимается тестированием компьютерных игр – не как работу по 5-9 часов в день, а как мечту всех подростков. Кто бы не хотел сидеть на комфортном диване и целый день играть в игры с небольшими перерывами на «подтягивание» графики в третьем уровне?

Обеденная катастрофа

Роб Ходжсон (Rob Hodgson), тестировщик видео игр, однажды проверял ранний билд мультиплеерной игры Fallen Earth в обеденное время, когда внезапно игровые серверы упали. Вскоре после этого они дали сбой еще раз. Озадаченный сотрудник попробовал воспроизвести баг, но его попытки не увенчались успехом — ни он сам, ни кто-либо другой из членов команды не смог понять, что же вызывает эту критическую ошибку.

«Все это казалось очень странным, — рассказывал мне Роб. — Мы попросили одного программиста проверить логи во время зависаний сервера, но после некоторого времени он лишь пожал плечами, будучи не в силах определить проблему. Выглядело все так, как будто кто-то достиг границ игрового мира, и наши серверы попросту задыхались и умирали в попытке отследить игрока. Однако никто из QA-команды не тестировал пределы мира, а дизайнеры ушли на обед!»

После некоторых размышлений Ходжсон и другие члены команды принялись расспрашивать других сотрудников, и в конце концов определили источник проблемы, укладывающий серверы на обе лопатки. «Оказывается, обеденный перерыв дизайнеров имел самое непосредственное отношение к этой проблеме. — говорит Ходжсон. — Дело в том, что один из парней, работающий в отделе дизайна, ставил свою лошадь на «автопилот», и уходил перекусить. Порой она просто находила на своем пути дерево и останавливалась. Но случались и дни, когда она уходила в бескрайние просторы за пределами игрового мира, в результате чего серверы просто сваривались вкрутую».

***

Начиная от игры Assassin’s Creed Unity и заканчивая версией Batman: Arkham Knight для ПК — складывается такое ощущение, что современные игры идут со значительно большим количеством проблем, чем раньше. Конечно же, легко взвалить всю вину на плечи отдела тестирования. И порой этому есть подтверждение — в частности, сразу несколько тестировщиков с большим опытом в QA-секторе сказали мне, что им довелось работать с равнодушными сотрудниками, которые небрежно относятся к своим обязанностям и выполняют их совсем не так, как следует. Два «матерых» тестировщика сообщили, что они не раз видели своих коллег курящими травку на обеденном перерыве практически каждый день, один из моих собеседников отметил, что некоторые даже носили темные очки в безуспешных попытках скрыть свои увлечения (судя по всему, чтобы не были видны расширенные зрачки — прим. переводчика).

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

Большинство тестеров работают, используя систему установления приоритетов, где каждому багу выставляется приоритет по критичности, важности его устранения. К багам с самым высоким приоритетам относятся ошибки, которые вызывают критический сбой в игре (вылет или зависание). Все остальные глюки причисляются к различным категориям в соответствии с уровнем важности — все зависит от позиции тестировщика. Как правило, продюсеры и программисты находят время на то, чтобы устранить критические ошибки, ведь с ними игре будет трудно даже пройти стандартную сертификацию. А вот мелкие ошибки и баги «средней тяжести» привязываются к игре, словно банный лист, будучи жертвами сжатых сроков завершения проекта и программистов, которые могут сделать ровно столько, сколько на это им было отведено времени — и не больше.

«Мы обычно приходим к выводу, что риск, сопутствующий исправлению бага, равно как и время, затраченное на решение проблемы, не стоят того — особенно когда мы находим ошибку, делая в игровом мире что-то совсем уж необычное или намеренно «ломая» игру», — рассказывает тестировщик, который работал над популярным ролевым боевиком The Elder Scrolls V: Skyrim. Последняя широко известна геймерам, будучи проектом с невиданным размахом и вместе с тем на редкость глючной игрой.

«Часть багов помечается как «не будут устранены» и «пофиксить после релиза». Последнее означает: «Было бы хорошо его устранить, но именно сейчас без этого можно обойтись. Вот если об этом баге люди будут писать на форумах и других онлайн-ресурсах, тогда, может быть, мы изменим его статус». Думаю, сейчас в игровой индустрии мы работаем со значительно большим запасом по времени, чем, скажем, 20 лет назад. Потому что сейчас разработчик/издатель просто может сказать: «Хорошо, мы приняли во внимание этот раздражающий баг, но его можно будет устранить с помощью day-one-patch (так называемый «патч первого дня», который выходит в день официального релиза игры — прим. переводчика)».

Игры становятся все больше, все масштабнее, развиваясь как в плане графики, так и в отношении геймплея и игровой механики. В результате этой эволюции генерируется все больше багов — в том числе тех, которые совсем непросто поймать или исправить. Студии разработки игр не могут изменить график выхода игры просто потому, что продукт наполнен глюками. Дата релиза продукта, приуроченная к какому-нибудь праздничному дню, не изменится, какая бы сырая игра ни была — если только издатель сам не согласится на перенос даты релиза, а это дорого ему обойдется. Поэтому, как сказали мне несколько тестировщиков, во время своей работы они сосредотачивают свои усилия на том, чтобы «выловить» именно те баги, которые не позволят им пройти обязательную сертификацию для выпуска на консоли. Обычно такое поведение подразумевает игнорирование части других, зачастую более значительных проблем в игре.

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

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

«Если нас поджимали сроки, а в мультиплеере до сих пор было множество глюков, то мы фокусировались на тестировании одиночного режима игры и других офлайн-составляющих продукта», — написал мне один из тестировщиков по электронной почте. — «Производители консолей могут легко забраковать игру, если у нее наблюдаются серьезные проблемы с мультиплеером, однако при этом они позволяют нам отправить игру в печать при том условии, что компания обеспечит выход «патча первого дня» для устранения проблем A, B и/или C. Такая политика используется потому, что ни разработчик, ни производитель консолей не в состоянии гарантировать получение ожидающейся «заплатки», устраняющей проблемы в офлайн-компонентах игры, ведь такие режимы не требуют интернет-подключения (геймер может играть в одиночном режиме без подключения к сети и материть разработчиков, не ведая о выходе патча — прим. переводчика). Поэтому мы работали над устранением тех багов, которые имели отношение к офлайн-содержимому, шлифуя лишь эту составляющую игры для успешного прохождения процедуры сертификации и осознавая, что в мультиплеере есть ошибки. А уже к моменту выхода игры мы старались подготовить патч, который должен устранить известные нам баги. К сожалению, это распространенная практика, которая зачастую применялась в условиях нереальных сроков завершения проекта и возникающей в результате этого слишком большой рабочей нагрузки. Обычно именно вследствие такого поведения (а не низкой квалификации команды разработчиков и тестировщиков) в игре оставалось множество багов».

Порой баги намеренно оставляют в игре по той или иной причине, непонятной для людей со стороны. Как пишет один тестировщик, работавший для американского подразделения легендарной игровой компании Nintendo, еще до международного выхода игры им удалось отловить известный баг, связанный с действием Sky Drop в игре Pokémon Black and White, однако к этому моменту уже состоялся релиз тайтла в Японии, поэтому компания вместо того, чтобы исправить глюк, решила оставить его в игре для сохранения паритета. В то же время тестировщик отмечает, что, помимо странной политики, Nintendo создала внутренний стандарт тщательной записи каждого бага, в соответствии с которым тестировщиков, выполняющих сабмит, просят приложить соответствующий видеоролик, который наглядно показывает «природу» найденной ошибки: «Мне кажется, это отлично способствует укреплению их репутации как компании, создающей качественные игры».

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

«Представим, что у нас есть игра с 40 DLC (элементами загружаемого контента). Даже если представить, что некоторые или даже все из этих 40 пакетов просто открывают скрытый контент, изначально размещенный на диске, мы все равно должны протестировать каждую возможную комбинацию этих 40 частей:

  • во всех игровых режимах;
  • используя различные физические носители для хранения информации (скажем, 20 DLC расположено на жестком диске, 15 — на USB-накопителе, 5 — на картах памяти, и т.д.);
  • сравнивая пакеты, установленные у владельца игры, с другими DLC, активированными у его соперников в мультиплеере;
  • проверяя надлежащее поведение игры в том случае, если у какого-то придурка все DLC хранятся на внешнем накопителе, и он вдруг решает отключить это устройство;
  • с каждым обновлением игры (1.01, 1.02 и т.д.);
  • с различными файлами сохранений;
  • и т.д. и т.п.

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

В результате нынешнюю ситуацию с тестированием можно описать в двух словах: все сложно. Особенно трудно тестировщикам работать с мультиплеерными играми: даже сотни тестеров порой не смогут воспроизвести такие баги, которые могут «словить» сотни тысяч людей, неустанно палящих в пришельцев на игровых серверах. И даже при том, что некоторые тестеры говорят, что сейчас разработчики более тесно и плодотворно сотрудничают с QA-командой, чем в прошлом, никто не в силах изменить нереальных дедлайнов.

«Устанавливаемые сейчас сроки завершения проектов абсурдны, — рассказывает один бывший тестировщик. — Судя по всему, большинство людей просто не понимают, что даже для игры, которая находится в разработке, скажем, 3 года, лишь половину из этого срока можно назвать временем полноценного производства. Лишь 9 месяцев игру тестируют, из них только 3 месяца тестирование идет в условиях максимальной загрузки QA-команды. В это время нам могут и вовсе закрыть доступ к игре, а к тому моменту, когда мы можем предоставить информацию по поводу продукта, игра уже получает бета-версию. Здесь я немного преувеличиваю, если говорить о больших известных тайтлах, но для большинства проектов, с которыми я работал, это вполне реальная ситуация. Когда QA-отделу наконец позволяют заговорить, оказывается, что на должную реакцию и соответствующие действия просто нет времени».

Прощание без обид

Рассказывает один бывший тестировщик: «На одном из проектов мне довелось работать в комнате с множеством других тестировщиков — эдакий большой «загон». Большинство из них выполняло свою работу на основе временного договора. Я сидел рядом с людьми, которые тестировали одну игру годами и были чертовски измучены этой работой, но продолжали работать, потому что у них не было другого выбора, они все еще надеялись. Насколько я помню, только двое или трое сотрудников из сотен тестировщиков получили постоянную работу. Нам регулярно приходили снисходительные письма такого рода: «Поздравляем! Мы только что заработали 8 часов отпуска!». Было такое ощущение, словно мы маленькие дети, а учителя рисуют нам на ладошке звездочку за прилежное поведение. Лично мне это было совершенно не нужно. Лучше проставьте вашу похвалу в платежную ведомость.

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

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

Можно ли построить успешную долгосрочную карьеру в QA? Или же эта сфера навсегда останется средством для достижения чего-то большего, эдаким транзитным пунктом для перехода в более интересные секторы игровой разработки?

Во время подготовки этой статьи я переговорил примерно с 60 тестировщиками, и только четверо из них сказали, что рассматривают возможность долгосрочной карьеры в QA. Многие использовали эту область для того, чтобы «перепрыгнуть» на должность продюсера или дизайнера в игровых компаниях, другие же работали тестировщиками на протяжении нескольких месяцев или лет, после чего сдавались и переходили в другие, более прибыльные рабочие области. Те же, кто чувствует в себе силы, чтобы остаться и преуспеть в тестировании, может попробовать себя в роли менеджера в QA-отделе — той специализации, где умение управлять другими людьми куда более важно, чем помощь в создании видеоигр. Для некоторых это, впрочем, не приносит ожидаемого удовлетворения, особенно учитывая тот факт, что стереотип о юных, незрелых, небрежных в работе тестировщиках зачастую соответствует действительности.

«Сразу же после начала работы на позиции менеджера, управляющего командой, ты получаешь такой опыт, к которому никого нельзя подготовить заранее», — рассказывает умудренный опытом тестировщик. — Тестеры приходят на работу в пижамах, устраивают друг другу сексуальные домогательства, крадут вещи… Даже такая элементарная вещь, как заказ еды может стать для менеджера настоящим испытанием. QA — это рабочая область, явно смещенная в сторону молодых «специалистов». Команда, которая состоит сплошь из 18-19-летних ребят — совершенно обычное явление, и зачастую это их первая серьезная работа. Управление такой командой — настоящее испытание для менеджера, которому приходится иметь дело с молодыми неокрепшими личностями и бушующими гормонами».

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

Еще один бывший тестер по имени Джеймс, который попросил не писать свою фамилию, рассказал следующее: «Размышляя о своем уходе, я попросил моего менеджера, помощника продюсера игрового проекта, уделить мне пару минут своего времени, чтобы спросить совета. Я хотел узнать, как можно перейти с должности временного тестировщика на постоянную позицию в области QA — например, в разработке или другом секторе. Он честно посоветовал мне дистанцироваться от тестирования вообще: «Чем быстрее ты сможешь уйти из этой области, тем лучше для тебя». Спустя восемь месяцев я таки ушел из QA, более того — я и вовсе покинул игровую индустрию. Сейчас я разработчик коммерческого ПО. Признаю, в моей жизни теперь стало меньше веселья, зато прибавилось стабильности, да и зарплата значительно выросла».

Основано на реальных событиях

Кинолента «Мальчик на троих» (Grandma’s Boys) которая вышла на экраны в 2006 году и образ актера Алана Коверта (Allan Covert), сыгравшим тестировщика, стала чем-то вроде шутки в мире QA. Люди постоянно спрашивают настоящих тестировщиков: что из показанного в фильме является правдой? Обычно ответом является «немногое».

Один из эпизодов фильма, в котором самовлюбленный гейм-дизайнер J.P. просматривает раннюю версию игры и видит, как у главного персонажа отваливается голова, часто относят к моментам, указывающим на несоответствие реальности (промотайте до 1:31):

Глупо, правда? Баги в действительности не проявляются вот так. За исключением особых, единичных случаев…

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

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

***

Фантазии геймеров из рекламы колледжа Уэствуд далеки от реального тестирования игр. В действительности мы имеем дело с непростой работой и низкими зарплатами. В результате многие тестировщики оказываются в незавидном положении, занимаясь аутсорсингом или выполняя подрядную работу для игрового разработчика, который даже не знает, кто делает для них тестирование. Один QA-специалист написал мне, что однажды он получил электронное письмо от HR-менеджера компании, где он работал: «Сотрудников приглашали написать свои мысли и пожелания по поводу праздничного корпоративного вечера. Когда же я ответил на письмо, предложив пару своих идей, оказалось, что сотрудники, работающие по временному договору, даже не приглашены на вечеринку».

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

«Тестирование видеоигр — это работа невоспетых и неквалифицированных героев, — сообщалось в аналитической статье от весьма известной в свое время компании ST Labs. — Для некоторых компаний типичная стратегия тестирования заключается в том, чтобы дать игру большому количеству тестировщиков накануне релиза и надеяться, что специалисты отыщут серьезные проблемы. Стремление понять игровую специфику и современные технологии, выстраивание методики систематичного, тщательного тестирования — все это до сих пор воспринимается большинством компаний-разработчиков как совершенно новый подход».

Эта статья была написана Дьюри Прайсом (Duri Price) и Ильей Перлманом (Ilya Pearlman) в далеком 1997 году. Спустя 18 лет и 4 поколения игровых консолей можно подтвердить, что ничего не изменилось. Хорошие игровые тестировщики до сих пор являются невоспетыми и неквалифицированными героями, которые делают все, что в их силах, чтобы устранить все-все баги в игре. Несмотря на абсурдные дедлайны и совершенно лишенных мотивации коллег, они делают свою работу, которую всегда недооценивают — в лучшем случае.

Впрочем, существует и другой взгляд на эту ситуацию.

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

P.S. Прошу прощения у сообщества Мегамозга за столь затянувшийся перевод второй части. Искренне надеюсь, что вам понравилось предложенное чтиво.

ссылка на оригинал статьи https://megamozg.ru/post/23714/

Cухой антипаттерн

Долгое время я задумывался, что же не в порядке с некоторыми частями кода. Раз за разом, в каждом из проектов находится некий «особо уязвимый» компонент, который все время «валится». Заказчик имеет свойство периодически менять требования, и каноны agile завещают нам все хотелки воплощать, запуская change request-ы в наш scrum-механизм. И как только изменения касаются оного компонента, через пару дней QA находят в нём несколько новых дефектов, переоткрывают старые, а то и сообщают о полной его неработоспособности в одной из точек применения. Так почему же один из компонентов все время на устах, почему так часто произносится фраза а-ля «опять #компонент# сломался»? Почему этот компонент приводится как антипример, в контексте «лишь бы не получился ещё один такой же»? Из-за чего этот компонент так неустойчив к изменениям?

Когда находят причину, приведшую к, или способствовавшую развитию такого порока в приложении, эту причину обозначают, как антипаттерн.
В этот раз камнем преткновения стал паттерн Strategy. Злоупотребление этим паттерном привело к созданию самых хрупких частей наших проектов. Паттерн сам по себе имеет вполне «мирное» применение, проблема скорее в том, что его суют туда где он подходит, а не туда, где он нужен. «Если вы понимаете о чем я» (с).

Классификация

Паттерн существует в нескольких «обличьях». Суть его от этого меняется не сильно, опасность его применения существует в любом из них.
Первый, классический вид — это некий долгоживущий объект, который принимающий другой объект по интерфейсу, собственно стратегию, через сеттер, при некотором изменении состояния.
Второй вид, вырожденный вариант первого — стратегия принимается один раз, на все время жизни объекта. Т.е. для одного сценария используется одна стратегия, для другого другая.
Третий вид, это исполняемый метод, либо статический, либо в короткоживущем объекте, принимающий в качестве входного параметра стратегию по интерфейсу. У «банды четырёх» этот вид назван как «Template method».
Четвертый вид — интерфейсный, ака UI-ный. Иногда называется как паттерн «шаблон», иногда как «контейнер». На примере веб-разработки — представляет из себя некий, кусок разметки, содержащий в себе плейсхолдер (а то и не один), куда во время исполнения отрендерится изменяемая, имеющая несколько разных реализаций, часть разметки. Параллельно разметке, в JavaScript коде также живут параллельные вью-модели или контроллеры, в зависимости от архитектуры, принятой в приложении, организованные по второму виду.

Общие черты этих всех случаев:
1)некоторая неизменяемая часть ака имеющая одну реализацию, далее, я буду называть ее контейнером
2)изменяемая часть, она же стратегия
3)место использования, создающее/вызывающее контейнер, определяющее какую стратегию контейнер должен использовать, далее буду называть это сценарием.

Развитие болезни

Поначалу, когда компонент, с использованием этого паттерна только реализовывался в проекте, он не казался таким уж плохим. Применен он был, когда было нужно создать две одинаковые страницы(опять же, на примере веб-разработки), которые лишь немного отличаются контентом в середине. Даже наоборот, разработчик порадовался, насколько красиво и изящно получилось реализовать принцип DRY, т.е. полностью избежать дублирования кода. Именно такие эпитеты я слышал о компоненте, когда он был только-только создан. Тот самый, который стал попаболью всего проекта несколькими месяцами позже.
И раз уж я начал теоретизировать, зайду чуть дальше — именно попытки реализовать принцип DRY, через паттерн strategy, собственно как и через наследование, приводят к тьме. Когда в угоду DRY, сам того не замечая, разработчик жертвует принципом SRP, первым и главным постулатом из SOLID. К слову, DRY не является частью SOLID, и при конфликте, надо жертвовать именно им, ибо он не благоприятствует созданию устойчивого кода в отличие от, собственно, SOLID. Как оказалось — скорее даже наоборот. Переиспользование кода должно быть приятным бонусом тех или иных дизайн-решений, а не целью принятия оных.
А соблазн переиспользования возникает, когда заказчик приходит с новой историей на создание третьей страницы. Ведь она так похожа на первые две. Еще этому немало способствует желание заказчика реализовать все «подешевле», Ведь переиспользовать ранее созданный контейнер быстрее, чем реализовать страницу полностью. История попала к другому разработчику, который быстро выяснил, что функционала контейнера недостаточно, а полноценный рефакторинг не вписывается в оценки. Ещё одна из ошибок тут в том, что разработчик продолжает следовать плану, поставленным оценкам, и это происходит «в тишине», ведь ответственности как бы нет, она лежит на всей команде, принявшей такое решение и такую оценку.
И вот в контейнер добавляется новый функционал, в интерфейс стратегии добавляются новые методы и поля. В контейнере появляются if-ы, а в старых реализациях стратегии появляются «заглушки», чтоб не сломать уже имеющиeся страницы. На момент сдачи второй истории, компонент уже был обречен, и чем дальше, тем хуже. Все сложнее разработчикам понимать как он устроен, в том числе тем, которые «совсем недавно в нём что то делали». Все сложнее вносить изменения. Все чаще приходится консультироваться с «предыдущими потрогавшими», чтобы спросить как это работает, почему были внесены некоторые изменения. Все больше вероятность того, что даже малейшее изменение внесёт новый дефект. Собственно уже речь начинает идти о том, что все больше вероятность внесения двух и более дефектов, ибо один дефект появляется уже с околоединичной вероятностью. И вот настает момент, когда новое требования заказчика реализовать невозможно. Выхода два: либо полностью переписать, либо сделать явный хак. А в ангуляре как раз есть подходящий хак-инструмент — можно сделать emit события снизу вверх, затем broadcast сверху вниз, когда закончил грязные делишки наверху. Технический долг при этом уже не увеличивается, он и так уже давно равен стоимости реализации этого компонента с нуля.

Сухая альтернатива

Наследование нередко порицается, а корпорация добра в своем языке Go вообще решила обойтись без него, и как мне кажется, негатив к наследованию частично исходит из опыта реализации принципа DRY через него. «Стратежный» DRY так же приводит к печальным результатам. Остается прямая агрегация. Для иллюстрации я возьму простой пример и покажу как он может быть представлен в виде стратегии, то есть шаблонного метода и без него.
Допустим у нас есть два сильно похожих сценария, представленных следующим псевдокодом:
В них повторяются 10 строчек X в начале и 15 строчек Y в конце. В середине же один сценарий имеет строчки А, другой — строчки B

СценарийА{ строчка X1 ...строчка X10 Строчка А1 ...Строчка А5 строчка Y1 ...Строчка Y15 } 

СценарийВ{ строчка X1 ...строчка X10 Строчка B1 ...Строчка B3 строчка Y1 ...Строчка Y15 } 

Вариант избавления от дублирования через стратегию

Контейнер{ строчка X1 ...строчка X10 ВызовСтратегии() строчка Y1 ...Строчка Y15 } 

СтратегияА{ Строчка А1 ...Строчка А5 } 

СтратегияВ{ Строчка B1 ...Строчка B3 } 

СценарийА{ Контейнер(new СтратегияА) } 

СценарийВ{ Контейнер(new СтратегияB) } 

Вариант через прямую агрегацию

МетодХ{ строчка X1 ...строчка X10 } 

МетодY{ строчка Y1 ...Строчка Y15 } 

МетодА{ Строчка А1 ...Строчка А5 } 

МетодВ{ Строчка B1 ...Строчка B3 } 

СценарийА{ МетодХ() МетодA() МетодY() } 

СценарийВ{ МетодХ() МетодB() МетодY() } 

Здесь предполагается, что все методы в разных классах.
Как я уже говорил, на момент реализации, первый вариант смотрится не так уж и плохо. Недостаток его не в том, что он изначально плох, а в том, что он неустойчив к изменениям. И, все же, хуже читается, хотя на простом примере это может быть и не очевидно. Когда нужно реализовать третий сценарий, который похож на первые два, но не на 100%, возникает желание переиспользовать код, содержащийся в контейнере. Но его не получится переиспользовать частично, можно лишь взять его целиком, поэтому приходится вносить изменения в контейнер, что сразу несет риск сломать другие сценарии. Тоже самое происходит, когда новое требование предполагает изменения в сценарии А, но это не должно затрагивать сценарий B. В случае же с агрегацией, метод X можно с легкостью заменить на метод X’ в одном сценарии, совершенно не затрагивая другие. При этом нетрудно предположить, что методы X и X’ могут также почти полностью совпадать, и их также можно подразбить. При «стратежном» подходе, если каскадировать таким же «стратежным» образом, то зло, помещаемое в проект возводится во вторую степень.

Когда можно

Многие примеры использования паттерна strategy лежат на виду, и часто используются. Их все объединяет одно простое правило — в контейнере нет бизнес логики. Совсем. Там может быть алгоритмическое наполнение, например хэш-таблица, или алгоритм поиска. Стратегия же содержит бизнес-логику. Правило, по которому один элемент равен другому или больше-меньше есть бизнес-логика. Все операторы linq также являются воплощением паттерна, например оператор .Where() также является шаблонным методом, и принимаемая им лямбда есть стратегия.
Кроме алгоритмического наполнения, это может быть наполнение связанное с внешним миром, асинхронные запросы например, или, в примере от «банды четверых» — подписка на событие нажатия мыши. То что называют коллбэками по сути есть та же стратегия, надеюсь мне простят все мои гипер-обобщения. Также, если речь идет о UI, то это могут быть табы, или всплывающее окно.
Словом, это может быть что угодно, полностью абстрагированное от бизнес-логики.
Если же вы в разработке используете паттерн strategy, и в контейнер бизнес-логика попала — знайте, линию вы уже перешагнули, и стоите по щиколотку в… ммм, болоте.

Запахи

Иногда непросто понять, где черта между бизнес-логикой и общими задачами программирования. И поначалу, когда компонент только создан, определить, что он в будущем принесет геморроя, непросто. Да и если бизнес-требования никогда не будут меняться, то этот компонент может никогда и не всплыть. Но если изменения будут, то неизбежно будут появляться следующий code-smells:
1. Количество передаваемых методов. Параметр обсуждаемый, сам по себе не вредный, но таки может намекнуть. Два-три еще нормально, но если в стратегии содержится с десяток методов, то наверное что-то уже не так.
2. Флаги. Если в стратегии кроме методов есть поля/свойства, стоит обратить на то как они называются. Такие поля как Name, Header, ContentText — допустимы. Но если видите такие поля как SkipSomeCheck, IsSomethingAllowed, это означает, что стратегия уже попахивает.
3. Условные вызовы. Cвязано с флагами. Если в контейнере есть похожий код, значит вы уже ушли в болото по пояс

if(!strategy.SkipSomeCheck) {   strategy.CheckSomething(). } 

4. Неадекватный код. На примере из JavaScript —

if(strategy.doSomething) 

Из названия видно, что doSomething это метод, а проверяется как флаг. То есть разработчики поленились создать флаг, обозначающий тип, а использовали в качестве флага наличие/отсутствие метода, причем внутри блока if даже не он вызывался. Если вы встречаете такое, знайте — компонент уже по горло в техническом долге.

Заключение

Еще раз хочу обозначить свое мнение, что первопричина всего того, что я описал, не в паттерне как таковом, а в том, что он использовался ради принципа DRY, и оный принцип был поставлен превыше принципа единственной ответственности, ака SRP. И, кстати, не раз уже сталкивался, с тем, что принцип единственной ответсвтенности как то не совсем адекватно трактуется. Примерно как «мой божественный класс управляет спутником, управлять спутником — это единственная его ответственность». На сей ноте хочу закончить свой опус и пожелать пореже в ответ на «почему так», слышать фразу «исторически так сложилось».

ссылка на оригинал статьи https://habrahabr.ru/post/275939/