В предыдущей статье я рассказывал как провести рефакторинг кода GildedRose с целью снижения time2market.

В результате из кода с цикломатической сложностью 19 получился код с цикломатической сложностью 5, при этом сам код изменился до неузнаваемости.

Было
#gilded_rose_cyclomatic_complexity_19.py

class GildedRose(object):

    ...

    def update_quality(self):
        for item in self.items:
            if item.name != "Aged Brie" and item.name != "Backstage passes to a TAFKAL80ETC concert":
                if item.quality > 0:
                    if item.name != "Sulfuras, Hand of Ragnaros":
                        item.quality = item.quality - 1
            else:
                if item.quality < 50:
                    item.quality = item.quality + 1
                    if item.name == "Backstage passes to a TAFKAL80ETC concert":
                        if item.sell_in < 11:
                            if item.quality < 50:
                                item.quality = item.quality + 1
                        if item.sell_in < 6:
                            if item.quality < 50:
                                item.quality = item.quality + 1
            if item.name != "Sulfuras, Hand of Ragnaros":
                item.sell_in = item.sell_in - 1
            if item.sell_in < 0:
                if item.name != "Aged Brie":
                    if item.name != "Backstage passes to a TAFKAL80ETC concert":
                        if item.quality > 0:
                            if item.name != "Sulfuras, Hand of Ragnaros":
                                item.quality = item.quality - 1
                    else:
                        item.quality = item.quality - item.quality
                else:
                    if item.quality < 50:
                        item.quality = item.quality + 1

Стало
#gilded_rose_cyclomatic_complexity_5.py

class GildedRose(object):

    ...
    

    def update_quality(self):
        self.items = [EntityFactory.create(item=item).update_quality().to_item() for item in self.items]

...

class ItemEntity:
    def __init__(self, item):
        name = item.name
        self.name = name
        self.sell_in = item.sell_in
        self.quality = item.quality
        self.is_conjured = name.startswith("Conjured ")
        self.quality_increase_factor = 1

    def increase_quality(self):
        self.quality = min(self.quality + 1 * self.quality_increase_factor, 50)

    def decrease_quality(self):
        self.quality = max(self.quality - 1 * self.quality_increase_factor, 0)

    def decrease_sell_in(self):
        self.sell_in = self.sell_in - 1

    def change_quality(self):
        self.decrease_quality()

    def change_quality_after_sell_in(self):
        self.decrease_quality()

    def update_quality(self):
        self.change_quality()

        self.decrease_sell_in()

        if self.sell_in < 0:
            self.change_quality_after_sell_in()

        return self

    def to_item(self):
        return Item(name=self.name, sell_in=self.sell_in, quality=self.quality)

class EntityFactory:
    def remove_conjured_if_exists(name):
        if name.startswith("Conjured "):
            return name[9:]
        else:
            return name

    def create(item):
        stripped_name = EntityFactory.remove_conjured_if_exists(item.name)

        if stripped_name == "Aged Brie":
            return AgedBrie(item=item)
        elif stripped_name == "Backstage passes to a TAFKAL80ETC concert":
            return BackstagePasses(item=item)
        elif stripped_name == "Sulfuras, Hand of Ragnaros":
            return Sulfuras(item=item)
        else:
            return ItemEntity(item=item)

class AgedBrie(ItemEntity):
    def change_quality(self):
        self.increase_quality()

    def change_quality_after_sell_in(self):
        self.increase_quality()

class BackstagePasses(ItemEntity):
    def change_quality(self):
        self.increase_quality()

    def change_quality_after_sell_in(self):
        self.quality = 0

    def decrease_quality(self):
        super().decrease_quality()

    def increase_quality(self):
        super().increase_quality()

        if self.sell_in < 11:
            super().increase_quality()
        if self.sell_in < 6:
            super().increase_quality()

class Sulfuras(ItemEntity):
    def decrease_quality(self):
        pass

    def decrease_sell_in(self):
        pass

Мне стало любопытно сможет ли ChatGPT описать, что делает этот код и реализовать в нем новое требование:

  • Сотворенные элементы изменяют качество в 2 раза быстрее, чем не сотворенные

Важное уточнение: рефакторинг кода я делал самостоятельно и опубликовал на GitHub в феврале 2023 года. Данная статья написана в марте 2023 года, а ChatGPT, обученный на данных 2021 года, никоим образом не мог видеть этот код и не мог знать о правильном решении.

Начало диалога с ChatGPT.

Я: Привет. Вот код ката Позолоченная роза <код программы под спойлером>

Спойлер
#gilded_rose_cyclomatic_complexity_5.py

class GildedRose(object):

    def __init__(self, items):
        self.items = items

    def update_quality(self):
        self.items = [EntityFactory.create(item=item).update_quality().to_item() for item in self.items]

class Item:
    def __init__(self, name, sell_in, quality):
        self.name = name
        self.sell_in = sell_in
        self.quality = quality

    def __repr__(self):
        return "%s, %s, %s" % (self.name, self.sell_in, self.quality)

class ItemEntity:
    def __init__(self, item):
        name = item.name
        self.name = name
        self.sell_in = item.sell_in
        self.quality = item.quality
        self.is_conjured = name.startswith("Conjured ")
        self.quality_increase_factor = 1

    def increase_quality(self):
        self.quality = min(self.quality + 1 * self.quality_increase_factor, 50)

    def decrease_quality(self):
        self.quality = max(self.quality - 1 * self.quality_increase_factor, 0)

    def decrease_sell_in(self):
        self.sell_in = self.sell_in - 1

    def change_quality(self):
        self.decrease_quality()

    def change_quality_after_sell_in(self):
        self.decrease_quality()

    def update_quality(self):
        self.change_quality()

        self.decrease_sell_in()

        if self.sell_in < 0:
            self.change_quality_after_sell_in()

        return self

    def to_item(self):
        return Item(name=self.name, sell_in=self.sell_in, quality=self.quality)

class EntityFactory:
    def remove_conjured_if_exists(name):
        if name.startswith("Conjured "):
            return name[9:]
        else:
            return name

    def create(item):
        stripped_name = EntityFactory.remove_conjured_if_exists(item.name)

        if stripped_name == "Aged Brie":
            return AgedBrie(item=item)
        elif stripped_name == "Backstage passes to a TAFKAL80ETC concert":
            return BackstagePasses(item=item)
        elif stripped_name == "Sulfuras, Hand of Ragnaros":
            return Sulfuras(item=item)
        else:
            return ItemEntity(item=item)

class AgedBrie(ItemEntity):
    def change_quality(self):
        self.increase_quality()

    def change_quality_after_sell_in(self):
        self.increase_quality()

class BackstagePasses(ItemEntity):
    def change_quality(self):
        self.increase_quality()

    def change_quality_after_sell_in(self):
        self.quality = 0

    def decrease_quality(self):
        super().decrease_quality()

    def increase_quality(self):
        super().increase_quality()

        if self.sell_in < 11:
            super().increase_quality()
        if self.sell_in < 6:
            super().increase_quality()

class Sulfuras(ItemEntity):
    def decrease_quality(self):
        pass

    def decrease_sell_in(self):
        pass

ChatGPT: Привет! Чем я могу помочь по этому коду?

Я: Опиши, что он делает

ChatGPT: Данный код реализует систему управления качеством товаров магазина, представленных в виде списка объектов класса Item. Каждый объект этого класса характеризуется названием товара (name), количеством дней до окончания срока годности (sell_in) и текущим качеством (quality).

Класс GildedRose содержит метод update_quality(), который обновляет состояние всех товаров в списке items в соответствии с правилами, определенными в классах‑ItemEntity`.

Классы‑наследники представляют конкретные типы товаров и определяют особенности изменения их качества во времени. Например, класс AgedBrie представляет товар «Сыр Пармезан», качество которого увеличивается со временем, а класс BackstagePasses представляет билеты на концерты, качество которых увеличивается более быстро, когда остается мало времени до концерта.

Класс EntityFactory используется для создания объектов классов‑наследников на основе названия товара.

Также в классе ItemEntity определены методы для изменения качества, уменьшения срока годности и проверки состояния товара (например, если срок годности закончился, качество должно ухудшиться быстрее). Метод to_item() используется для преобразования объекта класса ItemEntity обратно в объект класса Item.

Моя реакция: Поразительно! Все описано совершенно точно.

Я: Можешь реализовать требование: сотворенные элементы изменяют качество в 2 раза быстрее, чем не сотворенные?

ChatGPT: Да, конечно! Для реализации этого требования необходимо внести изменения в класс ItemEntity. Вместо фиксированного коэффициента 1 для изменения качества, мы будем использовать переменную quality_increase_factor, которая будет равна 2 для сотворенных товаров и 1 для обычных товаров.

Измененный код будет выглядеть так:

class ItemEntity:
    def __init__(self, item):
        name = item.name
        self.name = name
        self.sell_in = item.sell_in
        self.quality = item.quality
        self.is_conjured = name.startswith("Conjured ")
        self.quality_increase_factor = 2 if self.is_conjured else 1

    def increase_quality(self):
        self.quality = min(self.quality + 1 * self.quality_increase_factor, 50)

    def decrease_quality(self):
        self.quality = max(self.quality - 1 * self.quality_increase_factor, 0)

    def decrease_sell_in(self):
        self.sell_in = self.sell_in - 1

    def change_quality(self):
        self.decrease_quality()

    def change_quality_after_sell_in(self):
        self.decrease_quality()

    def update_quality(self):
        self.change_quality()

        self.decrease_sell_in()

        if self.sell_in < 0:
            self.change_quality_after_sell_in()

        return self

    def to_item(self):
        return Item(name=self.name, sell_in=self.sell_in, quality=self.quality)

Теперь при создании объекта класса ItemEntity переменная quality_increase_factor будет равна 2, если товар является сотворенным (is_conjured), и 1 в противном случае. Это позволит изменять качество сотворенных товаров в 2 раза быстрее, чем обычных товаров.

Моя реакция: Потрясающе! ChatGPT никак не мог знать заранее о переменной quality_increase_factor: ее нет в исходной головоломке, описанной в Интернет и в других решениях, которые я видел. По правде говоря, я добавил ее специально, чтобы заставить ChatGPT поломать голову — но она справилась с этой задачей.

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

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

Заключение

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

Изменение было внесено в 5 раз быстрее, чем это мог бы сделать человек.

Удачи в исследованиях!

Комментарии (11)


  1. anonymous
    00.00.0000 00:00

    НЛО прилетело и опубликовало эту надпись здесь


  1. Sadler
    00.00.0000 00:00
    +1

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

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

    Пример
    def pad_tensor_with_zeros(tensor, shape):
        """
        Pads a PyTorch tensor with zeros to the desired shape.
        Args:
            tensor: PyTorch tensor to be padded.
            shape: Desired shape of the padded tensor.
        Returns:
            Padded PyTorch tensor.
        """
        # Get the current shape of the tensor
        current_shape = tensor.shape
        
        # Compute the amount of padding needed for each dimension
        pad_sizes = []
        for i in range(len(shape)):
            pad_size = shape[i] - current_shape[i]
            if pad_size < 0:
                raise ValueError("Desired shape must be larger than current shape")
            pad_sizes.append(pad_size)
        
        # Create a tensor of zeros with the desired shape
        padded_tensor = torch.zeros(shape, dtype=tensor.dtype, device=tensor.device)
        
        # Copy the original tensor into the padded tensor
        slice_obj = tuple([slice(0, s) for s in current_shape])
        padded_tensor[slice_obj] = tensor
        
        return padded_tensor
    


  1. nidalee
    00.00.0000 00:00
    +1

    Насколько вырастает ваша продуктивность при использовании инструментов дополнения кода с использованием искусственного интеллекта?
    Проголосовал за 50%, но учитывая, что я не знаю Python, но теперь пилю на нем скрипты — это, наверное, ∞%


  1. spinmozg
    00.00.0000 00:00
    +2

    А что такое сотворенные товары?


    1. Andrey_Solomatin
      00.00.0000 00:00

      В оригинале conjured, имеется в виду "сотворённые магией".


  1. freeExec
    00.00.0000 00:00

    1) потратить 5 минут на доработку

    2) потратить 1 минуту на чат и еще 5 минут на проверку, правильно ли чат выдал

    Так себе экономия.


    1. vmihaylov Автор
      00.00.0000 00:00

      Тестировать в любом случае нужно независимо от того, поправил ли код программист или языковая модель. В крупных компаниях на этом, кстати, теряются драгоценные минуты Time2Market. По ссылке на предыдущую статью можно найти unit-тесты. С ними все получается проверить весьма быстро.


  1. superyarik
    00.00.0000 00:00

    Как у вас получается сначала отправить код программы и потом просить там что-то поправить? У меня бот сразу начинает что-то там сам переделывать при первом сообщении


    1. vmihaylov Автор
      00.00.0000 00:00

      Попробуйте начать диалог следущим образом:

      Привет. Вот код ката Позолоченная роза <код программы>


      1. superyarik
        00.00.0000 00:00

        именно так я и сделал, ответ такой:

        def changequalityaftersellin(self):
        pass


        1. vmihaylov Автор
          00.00.0000 00:00

          ????‍♂️