admin管理员组

文章数量:1443213

如何利用 AI 工具优化开发流程和时间分配

作为一个开发人员,笔者一直对那些下了班之后还坐在工位上磨洋工,靠一味延长工作时长,在领导那里刷存在感的行为很不以为然。

笔者认为,如果一件事情可以短时间内高效完成,那就没必要拖着,高效完成之后可以节省时间去做一些附加值更高的工作。

AI 工具普及之后,可以帮助程序员在很多流程和具体编码环节进行提速,从而能够省下很多时间。

本文仅仅聊一聊笔者本人,平时是如何用 AI 工具提高自己开发效率的。

1. 辅助单元测试代码生成

很多同行都不太喜欢写单元测试代码,因为和 feature 代码相比,觉得写 unit test 没什么成就感。

笔者工作中使用 Angular 进行前端开发:

以笔者上图显示的基于 Ngrx 编写的 effect 类为例,ChatGPT 生成了高质量的单元测试代码:

代码语言:typescript复制
import { TestBed } from '@angular/core/testing';
import { Observable, of, throwError } from 'rxjs';
import { hot, cold } from 'jasmine-marbles';
import { provideMockActions } from '@ngrx/effects/testing';

import { PageEffects } from './page.effects';
import { CmsActions } from '../actions/index';
import { AuthActions } from '../../../auth/user-auth/store/actions/index';
import { SiteContextActions } from '../../../site-context/store/actions/index';
import { CmsPageConnector } from '../../connectors/page/cms-page.connector';
import { RoutingService } from '../../../routing/index';
import { LoggerService } from '../../../logger';
import { CmsStructureModel } from '../../model/page.model';

// 如果没有导入 serializePageContext,则可以在测试环境中模拟(或确保其稳定性)
import { serializePageContext } from '../../utils/cms-utils';

describe('PageEffects', () => {
  let actions$: Observable<any>;
  let effects: PageEffects;
  let cmsPageConnector: jasmine.SpyObj<CmsPageConnector>;
  let routingService: jasmine.SpyObj<RoutingService>;
  let logger: jasmine.SpyObj<LoggerService>;

  beforeEach(() => {
    const cmsPageConnectorSpy = jasmine.createSpyObj('CmsPageConnector', ['get']);
    const routingServiceSpy = jasmine.createSpyObj('RoutingService', ['getRouterState']);
    const loggerSpy = jasmine.createSpyObj('LoggerService', ['log', 'error']);

    TestBed.configureTestingModule({
      providers: [
        PageEffects,
        provideMockActions(() => actions$),
        { provide: CmsPageConnector, useValue: cmsPageConnectorSpy },
        { provide: RoutingService, useValue: routingServiceSpy },
        { provide: LoggerService, useValue: loggerSpy },
      ],
    });

    effects = TestBed.inject(PageEffects);
    cmsPageConnector = TestBed.inject(CmsPageConnector) as jasmine.SpyObj<CmsPageConnector>;
    routingService = TestBed.inject(RoutingService) as jasmine.SpyObj<RoutingService>;
    logger = TestBed.inject(LoggerService) as jasmine.SpyObj<LoggerService>;
  });

  describe('refreshPage$', () => {
    it('should dispatch LoadCmsPageData action when router state meets criteria', () => {
      // 定义一个 page context 对象
      const pageContext = { id: 'home', type: 'ContentPage' };

      // 模拟路由状态,满足条件:cmsRequired 为 true 且 nextState 为 falsy
      const routerState = {
        state: {
          cmsRequired: true,
          context: pageContext,
        },
        nextState: null,
      };

      // 触发的 action 选择其中一个(这里以 LANGUAGE_CHANGE 为例)
      actions$ = hot('-a-', { a: SiteContextActions.LANGUAGE_CHANGE() });
      // routingService.getRouterState 返回一个冷 observable
      routingService.getRouterState.and.returnValue(cold('-b|', { b: routerState }));

      const expected = cold('--c', {
        c: new CmsActions.LoadCmsPageData(pageContext),
      });

      expect(effects.refreshPage$).toBeObservable(expected);
    });
  });

  describe('loadPageData$', () => {
    it('should dispatch success actions when cmsPageConnector returns data and page label differs from context id', () => {
      const pageContext = { id: 'home', type: 'ContentPage' };
      const components = ['comp1', 'comp2'];
      const cmsStructure: CmsStructureModel = {
        components: components,
        page: { label: 'homepage', otherProp: 'value' },
      };

      actions$ = hot('-a-', { a: new CmsActions.LoadCmsPageData(pageContext) });
      cmsPageConnector.get.and.returnValue(cold('-b|', { b: cmsStructure }));

      // 根据 effect 逻辑:
      // 1. 如果 page.label 不等于 context.id,则先 dispatch CmsSetPageSuccessIndex,
      // 2. 然后 dispatch CmsGetComponentFromPage,
      // 3. 最后 dispatch LoadCmsPageDataSuccess。
      const expectedActions = {
        c: new CmsActions.CmsSetPageSuccessIndex(
          { id: cmsStructure.page!.label, type: pageContext.type },
          cmsStructure.page!
        ),
        d: new CmsActions.CmsGetComponentFromPage(
          (cmsStructureponents || []).map(component => ({ component, pageContext }))
        ),
        e: new CmsActions.LoadCmsPageDataSuccess(pageContext, cmsStructure.page!)
      };

      // 合并后依次发出三个 action
      const expected = cold('---(cde)', expectedActions);
      expect(effects.loadPageData$).toBeObservable(expected);
    });

    it('should dispatch success actions when cmsPageConnector returns data and page label equals context id', () => {
      const pageContext = { id: 'home', type: 'ContentPage' };
      const components = ['comp1'];
      const cmsStructure: CmsStructureModel = {
        components: components,
        page: { label: 'home', otherProp: 'value' },
      };

      actions$ = hot('-a-', { a: new CmsActions.LoadCmsPageData(pageContext) });
      cmsPageConnector.get.and.returnValue(cold('-b|', { b: cmsStructure }));

      // 此时 page.label === context.id,只需要发出两个 action:
      // CmsGetComponentFromPage 与 LoadCmsPageDataSuccess
      const expectedActions = {
        d: new CmsActions.CmsGetComponentFromPage(
          (cmsStructureponents || []).map(component => ({ component, pageContext }))
        ),
        e: new CmsActions.LoadCmsPageDataSuccess(pageContext, cmsStructure.page!)
      };

      const expected = cold('---(de)', expectedActions);
      expect(effects.loadPageData$).toBeObservable(expected);
    });

    it('should dispatch fail action when cmsPageConnector returns an error', () => {
      const pageContext = { id: 'home', type: 'ContentPage' };
      const error = new Error('Test error');

      actions$ = hot('-a-', { a: new CmsActions.LoadCmsPageData(pageContext) });
      cmsPageConnector.get.and.returnValue(cold('-#', {}, error));

      // 在 catchError 中将错误通过 tryNormalizeHttpError 包装,这里为测试简单起见直接传递 error 对象
      const expected = cold('---(b)', {
        b: new CmsActions.LoadCmsPageDataFail(pageContext, error),
      });
      expect(effects.loadPageData$).toBeObservable(expected);
    });
  });
});

而且添加了简要说明:

  1. 测试依赖的模拟undefined利用 jasmine.createSpyObj 分别为 CmsPageConnectorRoutingServiceLoggerService 创建了 spy 对象,并将它们通过 TestBed 注入到 effect 中。
  2. refreshPage$ 测试
    • 触发 action(例如 SiteContextActions.LANGUAGE_CHANGE()),
    • 利用模拟的 routingService.getRouterState 返回一个满足条件的路由状态,
    • 最后检测是否会 dispatch 一个 CmsActions.LoadCmsPageData action,其 payload 为路由状态中的 context。
  3. loadPageData$ 测试undefined分为三种情况:
    • 正常成功时,如果返回的 cmsStructure.page.label 与 context.id 不同,则预期会依次 dispatch 三个 action。
    • 正常成功时,如果返回的 cmsStructure.page.label 与 context.id 相同,则只 dispatch 两个 action。
    • 当 cmsPageConnector 返回错误时,预期会 dispatch 一个 fail action。
  4. Marbles 测试undefined使用 hotcold 方法模拟 action 流与依赖的 observable。通过 toBeObservable 对比 effect 输出与预期的 action 流。

这样就节省了大量编写单元测试的时间。

2. 辅助作图

程序员无论工作还是写博客,都涉及到绘图的问题。

有了 AI 工具之后,我们可以让 AI 根据我们输入的文字进行作图。

举个例子, 我想画一张示意图,介绍剪映 app 中的蒙板功能:

蒙板这一功能在剪映 app 中扮演着至关重要的角色,其作用不仅仅局限于简单的图像遮挡,而是在视频剪辑和图像合成中实现局部效果处理、特殊过渡以及创意表达的一种高级手段。蒙板技术本质上利用一张灰度图像或形状轮廓来控制图像或视频中不同区域的透明度,进而使得用户能够精细地对部分画面进行滤镜叠加、剪裁、调整颜色或其他视觉效果。通过这项技术,剪映 app 为用户提供了一种灵活而强大的工具,使得视频制作不再局限于全局效果的处理,而可以对画面进行分区管理,实现层次丰富的视觉表现。

在蒙板功能的实现过程中,蒙板所对应的每个像素点往往会被赋予一个数值,该数值通常在 0 到 255 之间。这个数值表示该位置的透明程度,当数值为 0 时表示该区域完全透明;当数值为 255 时则完全不透明;介于二者之间的数值则实现渐变透明的效果。这样的设计允许用户在画面中通过调整蒙板的图像,使得某些区域能够显现出背景,而另一些区域则叠加了前景效果。可以将蒙板理解为一道“过滤网”,它定义了哪些部分需要被呈现,哪些部分需要被隐藏或者处理得更加细腻。

下面是 ChatGPT 根据上面我提供的文字,而绘制出的介绍剪映蒙板功能的图片,节省了我大量的手动作图时间。

本文标签: 如何利用 AI 工具优化开发流程和时间分配