Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Бессараб Дмитрий #31

Open
wants to merge 29 commits into
base: master
Choose a base branch
from

Conversation

d2em0n
Copy link

@d2em0n d2em0n commented Dec 25, 2024

@glazz200

Comment on lines 1 to 5
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

{
public interface ITextProcessor
{
public Dictionary<Word, int> Words(string path, ITextProvider provider, params IWordFilter[] filters);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Кажется фильтры - это всё-таки информация для конструктора, а не конкретного метода. Почему выбрал делать именно так?

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

var words = new Dictionary<Word, int>();
foreach (var word in GetWordsFromString(provider.ReadFile(path)))
{
var flag = true;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Очень абстрактное название для переменной. Давай придадим смысла

Comment on lines 20 to 26
foreach (var filter in filters)
{
if (flag == false) break;
if (filter.Skips(word)) continue;
flag = false;
}
if (flag == false) continue;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

flag = false;
}
if (flag == false) continue;
if (!words.TryGetValue(word, out var _))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь лучше подойдёт ContainsKey по значению

А ещё есть практика делать универсальный код с небольшими подстановками. Вот что имею в виду

// Подготовка данных, на случай если ещё нет ничего
if (!words.ContainsKey(word))
    words[word] = 0;

// Универсальное поведение для всех слов вне зависимости от происходящего
words[word] += 1;


namespace TagsCloudContainer.WordFilters
{
public class SimpleFilter : IWordFilter

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Может тогда BoringWordFilter?

{
public class SimpleFilter : IWordFilter
{
private readonly HashSet<string> _words =

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Дадим более узкое название этому списку исключений? А то только по операциям над ним становится понятно, что это такое

Font = font;
Color = color;
var rect = g.MeasureString(Value, Font).ToSize();
Frame = new Size(rect.Width + 2, rect.Height + 2);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Подскажи, для чего нужно добавлять магическое число к размерам?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Кстати, размеры можно считать "снаружи" и передавать уже готовый size. Тогда это станет record

Comment on lines 10 to 12
var provider = new TxtTextProvider();
var filter = new SimpleFilter();
var processor = new TextProcessor.TextProcessor();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Комментарий на будущее. DI контейнер заменит это на красивый блок кода, где не нужно будет задумываться над тем, как кого создать

}

[Test]
public void ThrowException()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не хватает в названии условия, при котором бросать исключение

Comment on lines 15 to 16
public bool RandomColor;
public Color Color { get; set; }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Названия у этих свойств не очень отражают суть. Получится придать им смысла? И ещё не очень понятно, как быть в ситуации, когда RandomColor=true и цвет тоже указан. Можно посмотреть в сторону nullable типов

var container = new ContainerBuilder();

container.RegisterType(config.PointGenerator)
.AsImplementedInterfaces()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

.AsImplementedInterfaces()
.SingleInstance();
else
{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Фигурные скобки сильно путают. Если у if их нет, то скорее всего в else тоже они не нужны


namespace TagsCloudContainer.PointGenerators
{
[Label("Спираль")]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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


namespace TagsCloudContainer.TagGenerator
{
public class RandomColorTagGenerator(ITextProcessor processor, Graphics graphics, Font defaultFont) : ITagsGenerator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Конструктор по умолчанию class Foo(prop1, prop2, prop3) может быть только у классов без методов (например record). Дело в том, что все эти поля создаются не readonly, что повышает риск ошибки

{
internal class Program
{
static void Main(string[] args)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если код ниже - эксперименты - хорошо. Но в итоговом решении его стоит вынести в отдельную реализацию клиента, и в Main вызывать уже его запуск


}

private static void ConfigureColor(Config config)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А вот вручную вызывать Assembly не нужно. Это ровно противоположное действие использованию DI контейнера. Задать возможные цвета через консоль поможет одна из библиотек парсинга аргументов. Например такой. Это заметно упростит обработку и поддержку кода. Чем более строгая типизация - тем лучше. Думаю, можно прямо на этапе разработки добавить нужные значения в аргументы. Они редко будут меняться. А что самое главное, при их изменении IDE подскажет, где есть использование, что позволит явно проконтролировать обратную совместимость (новая версия не ломает старое использование)

К тому же всё это теряет смысл в момент парсинга цвета, который производится вручную через switch

var pointGenerators = FindImplemetations<IPointGenerator>();
foreach (var point in pointGenerators)
Console.WriteLine(point.Key);
Console.WriteLine("Введите, соблюдая орфографию");
Copy link

@GlazProject GlazProject Jan 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Аналогично предыдущему. Если хочется дать такую гибкую возможность конфигурирования, то можно воспользоваться функцией di возвращать набор зарегистрированных реализаций (container.Resolve<IEnumerable<IPointGenerator>>()). Тогда пользователь сможет выбрать только из того, что заведомо работает и поддерживается.

А ещё писать целиком - это опасное дело. Приятнее для человека указать одну цифру, чем вручную писать целом слово

Font = font;
Color = color;
var rect = g.MeasureString(Value, Font).ToSize();
Frame = new Size(rect.Width + 2, rect.Height + 2);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Кстати, размеры можно считать "снаружи" и передавать уже готовый size. Тогда это станет record


public Tag(Graphics g, Word word, Font font, Color color)
{
Value = word.Value;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Точно нужно доставать значение из Word? Можно его сохранить как самодостаточную единицу, вдруг что-то понадобится в него добавить

Comment on lines +33 to +34
using var scope = container.BeginLifetimeScope();
scope.Resolve<PictureMaker>().DrawPicture();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines 1 to 14
using System.Drawing;
using System.Reflection;
using System.Threading.Channels;
using TagsCloudContainer.Configuration;
using TagsCloudContainer.PointGenerators;
using TagsCloudContainer.StringParsers;
using TagsCloudContainer.TextProviders;
using TagsCloudContainer.WordFilters;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.ComponentModel;
using TagsCloudContainer;
using TagsCloudContainer.TextProcessor;
using Autofac;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Очень много лишних using (

Comment on lines 63 to 64
var xLine = Console.ReadLine();
var yLine = Console.ReadLine();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

var xLine = ReadValue("Координата Х");
var yLine = ReadValue("Координата Y");
private static string? ReadValue(string? argName = null)
{
    Console.Write($"{argName ?? ""}: ");
    return Console.ReadLine();
}

Comment on lines 98 to 100
if (colors.ContainsKey(inp))
{
config.Color = Color.FromName(colors[inp].ToString());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

поток 1: colors.ContainsKey(inp) => true
поток 2: colors.Remove(inp)
поток 1: colors[inp] => KeyNotFoundException

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

Console.WriteLine("\t" + point.Key);
Console.WriteLine("Введите, соблюдая орфографию");
var pointGenerator = Console.ReadLine().ToLower();
if (pointGenerators.ContainsKey(pointGenerator))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Аналогично цвету, лучше использовать TryGetValue, но не критично здесь

var inp = Console.ReadLine().ToLower();
if (colors.ContainsKey(inp))
{
config.Color = Color.FromName(colors[inp].ToString());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Color.FromName - почти наверняка сработает, так как поддерживает много цветов. Но безопаснее было бы сделать в библиотеке в качестве параметра конструктора у класса GolorProvider значение enum, а потом его уже в switch преобразовывать в цвет. Таким образом, если вместо color придётся использовать какой-нибудь UnixColor, для внешних пользователей ничего не поменяется в использовании

[TestFixture]
public class CloudLayoutShould
{
private CloudLayout layout;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

public void SetRightFontSize()
{
var bitmap = new Bitmap(1, 1);
using var graphics = Graphics.FromImage(bitmap);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Графика нигде не используется


namespace TagsCloudContainer.Tests
{
public class RandomColorTagGeneratorShould

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Это ведь тест на два независимых объекта: TagGenerator и RandomColorProvider. Сложно будет понять, из-за кого пошло не так, при последующей разработке

public void Process()
{
var result = new TextProcessor.TextProcessor(
new TxtTextProvider(@"TextFile1.txt"), new RegexParser(), new BoringWordFilter(), new ShortWordFilter()).WordFrequencies();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Надежнее сделать какой-нибудь тестовый TextProvider, который будет просто текст из строки возвращать. Это избавит от возможных ошибок из-за работы TxtTextProvider и слова будут на виду в тесте


public class ColorProvider : IColorProvider
{
[CompilerGenerated] private readonly Color _color;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Думаю, здесь не нужен системный атрибут [CompilerGenerated], ведь этот код пишешь ты сам

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants