.NET Multi-platform App UI – фреймворк, который пишут профессионалы. Тем не менее, код некоторых его функций выглядит так, будто разработчики забыли о последствиях разыменования нулевых ссылок.
Небольшая вводная
Не берусь судить об общем качестве проекта MAUI, но у меня есть сильные подозрения, что проекту предстоит пройти долгий путь фиксов по фидбеку от пользователей про неожиданные NullReferenceException.
Код создаёт впечатление, что авторы очень торопятся при его написании. Из-за этого во многих местах без проверок разыменовываются ссылки, которые, по мнению самих же разработчиков, могут быть нулевыми. Чтобы было понятнее, давайте взглянем на отдельные фрагменты из исходников MAUI 6.0.424.
Почему так?
Именно этот вопрос я задавал себе, когда глядел на фрагменты, представленные далее. Либо я чего-то не понял, либо разработчики MAUI пишут очень странный код.
К примеру, взгляните на фрагмент из функции AttachEvents класса AdaptiveTrigger:
void AttachEvents()
{
....
_visualElement = VisualState?.VisualStateGroup?.VisualElement;
if (_visualElement is not null)
_visualElement.PropertyChanged += OnVisualElementPropertyChanged;
_window = _visualElement.Window; // <=
....
}
Тут выходит какая-то ерунда. Судя по двум "?." и проверке "is not null", в поле _visualElement может быть null. И буквально тут же мы видим обращение к свойству Window, но уже без проверки. Видимо, проглядели.
Ладно, идём дальше. Взгляните на фрагмент из класса FormattedString:
void OnCollectionChanged(....)
{
....
foreach (object item in e.OldItems)
{
var bo = item as Span;
bo.Parent = null; // <=
if (bo != null)
{
....
}
....
}
Опять соседние строчки. Сначала разыменовали bo, а затем вдруг решили проверить его на null. Я не знаю, падает ли этот код в каких-то случаях, но выглядит это всё странно.
А вот кусочек из класса ListView:
protected override void SetupContent(Cell content, int index)
{
....
if (content != null)
_logicalChildren.Add(content);
content.Parent = this;
VisualDiagnostics.OnChildAdded(this, content);
}
Тут снова какая-то ерунда. Почему в коллекцию элемент добавляется с проверкой, а к свойству мы обращаемся уже без неё?
Ещё одну "полезную" проверку на null я нашёл в конструкторе класса ShellChromeGallery:
AppShell AppShell => Application.Current.MainPage as AppShell;
public ShellChromeGallery()
{
....
if (AppShell != null)
{
flyoutBehavior.SelectedIndex = ....;
flyoutHeaderBehavior.SelectedIndex = ....;
}
else
{
flyoutBehavior.SelectedIndex = 1;
flyoutHeaderBehavior.SelectedIndex = 0;
}
AppShell.FlyoutBackdrop = SolidColorBrush.Pink; // <=
}
Ну тут опять всё круто: проверяем на null, выставляем при необходимости какие-то дефолтные значения, а потом сразу же падаем.
Учитывая то, как часто попадаются странные фрагменты, я даже начал думать, что проверка на равенство null – совсем не то, чем кажется. Ну, к примеру, в том же Unity оператор "==" перегружен и проверка на равенство null там работает по-особому. Однако в MAUI ничего такого я не увидел.
А почему же всё-таки так?
MAUI пишут профессионалы, но проект очень уж большой и сложный. Люди ошибаются, и это нормально. Странно другое: такие проблемы вполне могут отлавливать различные автоматизированные инструменты. К примеру, как несложно догадаться, я нашёл показанные фрагменты с помощью C# анализатора PVS-Studio. На мой взгляд, подобные инструменты сильно бы упростили жизнь разработчикам MAUI. А в целом желаю им успехов с развитием проекта.
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Nikita Lipilin. Do you plan to take on .NET MAUI? Get ready for an adventure with NullReferenceException.
Комментарии (23)
LordDarklight
06.10.2022 13:33+2Сырой этот MAUI - очень сырой - попробовал потыркал я его на .net 6 - да и забил - функциональность кастомизации визуализации очень слабая - и всё через одно место. Некоторых тривиальных вещей до сих пор нет в кроссплатформенном исполнении.
Плтаформенозависимой код тоже как-то глючный - вот мне так и не удалось заставить компилироваться windows-зависимый код (само собой корректно размещённый в платформенном windows файле) - ну ни в вкакую не хотел он определяться как подходящий к выбранной цели компиляции windows-machine. Решение тут похоже только одно - релизи такой код в отдельную .NET сборку и подключать (свои так ещё не делал - но сторонние подключал - ибо никак иначе не работало - весь не кроссплатформенный в базовом проекте не доступен - пространства имён отсутствуют или пустые).
Даже предефайны (так старательно разложенные по в VS2022 в настройках проекта для каждой цели компиляции) для windows-machine не подхватывались.
Офф поддержки Linux платформы не предвидится вовсе (а на кой он тогда вообще сплющился это MAUI - под мобилки и MacOS собирать - больно надо - под мобилки и Xamarin хватал - так его и под Linux можно было даже). Что опять возвращаться к Mono? Или ждать неофф поддержку Linux для MAUI. И на кой они тогда вообще встраивали гарфическое ядро в Linux в Windows - коли нормально под него программу не разработать! Больше то в нём (я о граф ядре Linux в Windows) особого смысла то и нет.
Пока Avalonia куда симпатичнее выглядит - хотя до 1.0 релиза ещё не добралась!
В общем, в топку этот MAUI - в .NET 7 RC1 его до сих пор нет - такое впечатление, что и не будет вовсе! И Если поставить .NET 7 RC1 - то его не будет и в .NET 6 проектах!
Firensis Автор
06.10.2022 14:18+2Это всё печально, если честно. Идея-то прикольная(
LordDarklight
07.10.2022 13:21Это я ещё не упомянул, что визуального редактора вовсе нет, и не факт что будет. Писать XML тегами можно и IDE немного помогает - но всё-равно очень не удобно (но это может дело привычки - много нюансов нужно учитывать - чтобы всё работало).
Отдельное негодование событийной модели визуальных элементов - в одном месте это событие элемента - в другом - команда - и всё по-разному присоединяется и обрабатывается! И даже одно и то же событие в разных элементах может по-разному называться!
То же и про свойства - в разных элементах могут быть разные свойства для одного и того же результата.
и IDisposable правит балом - вот получишь ты ссылку на объект - сохранишь в переменную на будущее - а объект потом раз - и уже Disposed - хотя объект с ним связанный жив и здоров! Причём в сторонних примерах по MAUI это всё вот так и используется - не знаю как оно у них работало...
И документация по MAUI на данный момент очень ограниченная - вроде бы материалы есть, и даже некоторые справочники по элементной базе есть - но всё как-то через одно место - в разнобой - перекрёстные ссылки настроены плохо, примеры - тривиальные - и долго долго раскручивают простой код с копипастом!
imlex
06.10.2022 14:18Я вот другого не понимаю. Ну не пользуются они статическими анализаторами (ну или не смотрят на отчёты, так как багов и так дофига). Ну и ладно. Но почему не используется Nullable reference types?
Firensis Автор
06.10.2022 14:20Кстати да - насколько я видел, оно у них выключено. С другой стороны, эта штука реально много ложек выдаёт и это мб раздражает. Но сторонние платные инструменты всё же поумнее в этом могут быть (просто потому, что работают не "на лету" и учитывают кучу всяких дополнительных особенностей и т.п.).
Pastoral
Писать такую статью без примеров того, как код падает - нельзя. Такая картина может наблюдаться не только когда == не то о чем кажется, но и когда .Parent не то, чем кажется.
Не ждал бы пока MAUI нормально выйдет из беты и станет реально кросс-платформенным, до того он мне даже на посмотреть без надобности, вообще писал бы коммент другим тоном.
Firensis Автор
Не совсем понял вас. Я не писал о том, что что-либо падает. Я писал о том, что разработчики будто бы сами не понимали, какие данные они хотят обрабатывать. Вы не согласны с тем, что представленный код выглядит странно? (при этом даже не содержит ни одного комментария типа "тут всё окей, так надо").
По поводу ".Parent". Вообще я не думаю, что это окей, когда обращение к свойству экземпляра это "не то, чем кажется". Кажется, это какая-то нездоровая тема. С другой стороны, для повышения собственного уровня образованности мне было бы правда интересно узнать о том, как можно обратиться к свойству у null так, чтобы не было исключения. Если у вас есть мысли на этот счёт, то прошу поделиться :).
Pastoral
Extension method?
Firensis Автор
Мы вроде говорили про свойство :). Да и анализатор, которым я собственно и нашёл все эти моменты, учитывает такие кейсы.
Pastoral
Тогда откуда рассуждения про == не то, что кажется? Вы же понимаете - такой код за гранью любой допустимости, а не странный. И статья написана на половину про то, какой анализатор классный. И без элементарного примера - и то и то ни разу не убедительно.
Потому что проще поверить что x.y стал overloadable чем в то, что с null упадёт.
Firensis Автор
Рассуждения про "==" связаны с тем, что в Unity сравнение с null через "==" и правда работает не совсем так, как привычно (документация).
Про анализатор я вроде как всего раз упоминаю, больше концентрируясь не на том "как невероятно круто что он нашёл", а просто на том, что в столь серьёзном проекте тут и там такой вот, как вы выразились, код "за гранью любой допустимости".
Потому что проще поверить что x.y стал overloadable чем в то, что с null упадёт.
Это уже вопрос мнения. Откровенно говоря - вы бы нормально отнеслись бы к тому, что такой код есть на вашем проекте? Ну вроде как "работает и ладно".
Pastoral
Я бы в любом случае отнёсся плохо, но тогда бы это была бы противоположная проблема - умничание.
Что касается темы статьи, то она не первая такая, проекты меняются, PWS-Studio остаётся. Что, кстати, даёт результат - я уже твёрдо выучил как она называется.
Firensis Автор
Ну видите, не зря пишем) Ещё пара статей, и даже не будете ошибаться в названии).
В любом случае, спасибо за диалог, было интересно услышать вашу позицию.
LordDarklight
Вот вот - MAUI - пока в бете - я бы сказал - даже в альфе - настолько сырой, что пока вырезан напрочь из .NET 7 - думаю пока там особо никто не следит за чистотой кода - вот буду релиз кандидат готовить - вот тогда и буду вычищать - думаю у мелкомягких должен быть инструмент для анализа кода (типа PVS - может даже PVS) - хотя если верить статьям от PVS-Stuidio - то ошибки они вылавливают то тут то там - в исходниках уже финально отрелизинных проектов).
Есть, конечно ещё вариант - что они намеренно в исходниках эти баги оставляют - а в бинарники пускают другие исходники - уже исправленные - но это прям как-то маловероятно! Но некоторую практическую ценность это могло бы нести (это как с высокотехнологичным вооружением - что на экспорт идёт - в нём всегда заложенные недостатки, по сравнению в оригинальным - думаю в софте тоже самое практикуется - свой инструмент должен быть лучше, чем такой же - но переданный другим)
Firensis Автор
Просто это не особо логично - писать сразу ВЕСЬ код огромного проекта "кое-как" безо всяких там инструментов и прочего, а потом в конце за неделю до релиза обнаружить миллион срабатываний и спешно разгребать, какие из них там ложные, какие нет и т.п.
LordDarklight
По-моему логично - зачем сразу вбухивать кучу ресурсов в идеально чистый код - когда:
Его может ещё не раз полностью или хотя бы частично перепишут (всё-таки по офф релиза ещё не было даже)
Это время лучше потратить на новые элементы функциональности - ведь пока MAUI ну очень сильно ограничен в функциональности
Да и пробная версия выпущена - пущай сообщество само баги ищет - и пишет "иссусу"- такова, кажется, новая политика открытого исходного кода у Microsoft
Но я не утверждаю, что они совсем уж не следят за кодом - во-первых сейчас уже IDE VS 2022 сама очень даже хорошо следит за багами в коде (спасибо Roslyn и диагностикам CodeAnalysis); во-вторых у них там наверняка и свои диагностики могут быть подключены - для реализации проверки каких-то внутренних стандартов; в-третьих - разработчики у мелкомягких пишут тесты - и пишут их много - т.е. конечный код в итоге обязательно тестируется в хост и в гриву - что помогает найти большинство допущенных багов; в-четвёртых - вероятно (я надеюсь) код пишут всё-таки опытные программисты, набившие и шишек, и прочитавшие книжки по чистому коду, прослышавшие лекции - т.е. на них может делаться ставка как на редко допускающих грубых ошибок. То есть - баги могут быть из-за человеческого фактора (все ошибаются) по невнимательности или из-за спешки. Да и, как уже было замечено - в статье то ни одного потенциального бага так и не воспроизвели - может просто инструмент проверки видит случаи, которые пока не воспроизводимы в принципе! Да, конечно, это не факт и не факт что так и будет в будущем - но.... идеальных проверок не бывает - в любой сложной программе так или иначе всегда будут ошибки, как в принципе воспроизводимые сейчас, так и проявляющиеся только в новых релизах после появления нового кода.
А всё остальное - это лишь ваше негодование - вот как
плохожаль, что не воспользовались нашим инструментом - он у нас такой хороший - реклама да и только - но ничего против такой рекламы не имею! А мелкомягкие может пользуются другим инструментом - вот же SonarQube например или ещё каким-то - который чем-то лучше, чем-то хуже PVS Studio. Может и PVS Studio у них стоит - но пока дело не дошло до финальных релизов - часть диагностик в нём отключена - для ускорения процесса диагностики большого проекта, ну и чтобы не отвлекать разработчиков по мелочам (когда и так полно более важных проблем вылезает) - вот будет готов проект - тогда и займутся его шлифовкой!Firensis Автор
Ну во-первых, никакого "негодования" у меня нет. И в статье не написано нигде про то, что "как же это они так вот не пользуются PVS". Я совершенно не сомневаюсь, что есть и другие решения, которые могут такое ловить. Возможно, среди них и приведённый вами SonarQube.
В-вторых - приведённые мною фрагменты настолько элементарны, что написать их сразу нормально не было бы дольше или дороже. Особенно это хорошо доказывает тот факт, что соответствующий issue они поправили буквально за день.
Да и речи не идёт про "идеально чистый код". Просто то, что написано у них, выглядит ну как-то совсем небрежно. В чём проблема прогнать анализатор (хоть наш, хоть любой другой) на изменённых файлах перед коммитом и пофиксить за 5 минут все странности, чтобы они уж точно не выстрелили в будущем?
Да, указанные косяки может никогда и не выстрелят в принципе. А может выстрелят. Какой смысл рисковать, если можно просто взять и пофиксить (как собственно и сделали, когда я открыл issue)?
И кажется, что очевидно, что проще всего пофиксить код, который написан тобой только что, чем какой-нибудь застарелый кусок, который добавили условно год назад люди которых уже мб и не сыскать. Смотришь на такой вот кусок и думаешь - чё тут хотели сделать? Может тут быть null или нет? Почему так?
LordDarklight
Вот смотрю я на только что написанный мной кусок кода - и мне уже хочется его переписать - но на это времени нет - нужно писать другой кусок кода - а вот когда всё заработает... ну или в очередной раз всё перепишу нахрен!
А пока мой код и так пухнет старыми функциями - которые уже были переписаны и уже не будут использоваться - или будут - когда новая реализация не оправдает ожижений на практике?
Firensis Автор
Я полностью согласен с вами, не нужно уходить в крайности. Но одно дело "код не нравится", а другое - "код выглядит стрёмно". И главное - какова цена вопроса? Потратить секунду на то, чтобы передвинуть разыменование под проверку) Во всяком случае, указанные мною места были примерно так и поправлены.
Важен баланс, короче говоря :).
LordDarklight
Чтобы код не выглядел стрёмно его нужно просто вылизывать донельзя - а это очень хлопотно - и никто делать это, пока проект не заработает согласно базовой спецификации в версии 1.0, в здравом уме не будет! Другое дел - это какая уже версия 3.2 хотя бы - вот тут уже можно и уборкой кода заняться!
Ну а чтобы таких ситуаций было поменьше - нужно переходить к декларативному программированию с глубокой поддержкой AI - который буде разбирать декларацию намерений и по ней уже генерировать сам код, к нему авто тесты и диагностики.
Понятно, что весь код декларативно не написать - поэтому должно быть два уровня:
Уровень алгоритмов - тут все диагностики и тесты должны делаться практически "не отходя от кассы", а сами алгоритмы должны быть как можно более компактными!
Уровень приложений - тут уже прикладное программирование и нюансы описания не так важны - главное, чтобы AI помощник понял что нужно - всё остальное он качественно сделает сам! И даже профайлинг замутит. И статистику боевого использования соберёт - и если что не так - сам внесёт правки - ведь он знает что нужно получить - а если потребуется - сам набросает issue разработчику - если в каких-то прикладных декларациях будут проблемные места!
Это, конечно, пока будущее - но к нему нужно стремиться! А не зацикливаться на выписывании сложного кода!
Firensis Автор
Мне кажется, мы говорим о разных вещах, LordDarklight. Определённо, то о чём вы рассказываете, крайне интересно. И всё это было бы круто, и к этому надо стремиться. Я бы даже хотел почитать что-нибудь на эту тему - если не сложно, скиньте каких-нибудь ссылок, пожалуйста. К сожалению, к теме статьи рассуждения об идеальном будущем не очень относятся и отдают максимализмом.
Статья говорит о том, что чуваки не подвинули конструкцию то на строчку-другую вниз, то на строчку-другую вверх. Причём увидеть эти моменты ну очень уж легко, если пишешь функцию хотя бы с одним открытым глазом. И вы на это отвечаете, что для того, чтобы такого не было, нужно будет "вылизывать код донельзя". Почему?
Почему код нужно прям обязательно "вылизать донельзя"? Неужели есть только 2 подхода - идеальная чистота и идеальная помойка? Типа
Всех странностей мы всё равно не пофиксим, не будем тратить наши драгоценные 10 секунд и на то, чтобы передвинуть разыменование под проверку. Вот будет версия 3.2 и прям отправимся все вместе убираться в этом море кода, в котором уже никто и не помнит, по какой причине что и как было сделано.
Поправить такие моменты как раз и легче всего в момент, когда они только-только написаны. Именно в такие моменты их править быстро и безболезненно. Ну не хотите-не можете вы внимательно просматривать всё, что комитите - ну хоть пусть какая-нибудь автоматическая тула глянет. Если вы внедрили её правильно, то она на коммит ну не выдаст супер-много срабатываний, им там неоткуда взяться.
Кстати, может "в здравом уме" фиксить эти проблемы и не станет никто, но, как я упомянул ранее, стоило мне про эти места написать, и тут же всё пофиксили. Как ни странно, но вот нашёлся человек, который смог внести столь сложные правки, как перенос разыменований под проверку). И я очень надеюсь, что он знал, что делал, и что действительно в тех фрагментах правка должна быть именно такой.