博客

Improving A Personal Programming Process Using Sensei

December 7, 2020
Alan Richardson

For this post, I've recreated a 'bad' coding approach that I used when I was learning JUnit, and will demonstrate how to convert the "bad'pattern to an agreed, and "better', coding pattern using Sensei.

When I was learning JUnit, I could only keep so much in my head at any one time. I constantly forgot how to skip tests when they were not working.

If we are working in a team then we can use code reviews on pull requests to help enforce coding styles. And we can shorten the feedback cycle when pair programming with a more experienced programmer.

We can also augment our process with tooling and have the tools prompt us to do the right thing. Thoughtworks described this as "tools over rules," in their Technology Radar listing for Sensei, to: "make it easy to do the right thing over applying checklist-like governance rules and procedures"


Disabling a JUnit Test


Ideally, I would, as we all know, use the @Disabled annotation and write:

  @Disabled
  @Test
  void canWeAddTwoNumbers(){
        Assertions.fail("this test was skipped and should not run");
   }

But, when learning, I had to train myself to use @Disabled.

When I forgot how to disable a Test method I would remove the @Test annotation and rename the test:

class SkipThisTest {
    void SKIPTHIScanWeAddTwoNumbers(){
        Assertions.fail("this test was skipped and should not run");
    }   
}


It wasn't good, but it got the job done. I didn't have something like Sensei to help me remember and so I fell into using poor coding patterns.

The tasks I've taken on board for this post are to:

  • Create a rule which finds methods that have been 'skipped' or 'disabled' by renaming the method.
  • Create a QuickFix to rename the method and add both an @Test and @Disabled annotation.


Recipe Settings

The first step I take with Sensei is to "add new recipe" and search for the coding pattern I want the recipe to act on.

Name: JUnit: Make @Disabled @Test from SKIPTHIS

Short Description: Stop naming methods SKIPTHIS, use @Disabled @Test instead


And my search is very simple. I use a basic regex to match the method name.

search:
   method:
       name:
            matches: "SKIPTHIS.*"


Recipe settings to skip a test


QuickFix Settings

The QuickFix is a little more complicated because it will rewrite the code, and I'll use a few steps to achieve my final code.

I want to:

  • add an @Test annotation to the method
  • add an @Disabled annotation to the method
  • amend the method name

Adding the annotations is simple enough using the addAnnotation fix. If I use a fully qualified name for the annotation then Sensei will automatically add the imports for me.

availableFixes:
- name: "Add @Disabled and @Test Annotation"
  actions:
  - addAnnotation:
       annotation: "@org.junit.jupiter.api.Test"
  - addAnnotation:
      annotation: "@org.junit.jupiter.api.Disabled"


The actual renaming seems a little more complicated but I'm just using a regex replacement, and the generic way to do this with Sensei is to use sed in a rewrite action.

Because the rewrite actions are Mustache templates, Sensei has some functional extensions in the template mechanism. A function is represented with {{#...}} so for sed the function is {{#sed}}. The function takes two comma-separated arguments.

The first argument is the sed statement:

  • s/(.*) SKIPTHIS(.*)/$1 $2/

The second argument is the String to apply the sed statement to, which in this case is the method itself, and this is represented in the Mustache variables as:

  • {{{.}}}

Giving me the rewrite action of:

 - rewrite:

       to: "{{#sed}}s/(.*) SKIPTHIS(.*)/$1 $2/,{{{.}}}{{/sed}}"



The sed implementation requires that when the arguments themselves contain commas, they are wrapped with {{#encodeString}} and {{/encodeString}} - e.g. {{#encodeString}}{{{.}}}{{/encodeString}}

Reverse Recipe

Since this is an example, and we might want to use this in demos, I wanted to explore how to reverse out the above change using a Sensei recipe.

Thinking it through I want to find a method annotated with @Disabled but only in the class SkipThisTest where I do the demo:

Name: JUnit: demo in SkipThisTest remove @Disabled and revert to SKIPTHIS

Short Description: remove @Disabled and revert to SKIPTHIS for demo purposes in the project

Level: warning


The Recipe Settings Search is very simple, matching the annotation in a specific class.

search:
  method:
    annotation:
      type: "Disabled"
    in:
      class:
        name: "SkipThisTest"


To avoid making the code look like it is an error I defined the general setting on the recipe to be a Warning. Warnings are shown with highlights in the code and it doesn't make the code look like it has a major problem.

For the Quick fix, since we have matched the method, I use the rewrite action and populate the template using the variables.

availableFixes:
- name: "Remove Disabled and rename to SKIPTHIS..."
  actions:
  - rewrite:
      to: "{{{ returnTypeElement }}} SKIPTHIS{{{ nameIdentifier }}}{{{ parameterList\
        \ }}}{{{ body }}}"


I add every variable except the modifier (since I want to get rid of the annotations) and add the SKIPTHIS text into the template.

This fix has the weakness that by removing the modifiers, I remove any other annotations as well.


Add another Action

I can add another named fix, to give me a choice when the alt+enter is used to display the QuickFix.

availableFixes:
- name: "Remove Disabled and rename to SKIPTHIS..."
  actions:
  - rewrite:
      to: "{{{ returnTypeElement }}} SKIPTHIS{{{ nameIdentifier }}}{{{ parameterList\
        \ }}}{{{ body }}}"
      target: "self"
- name: "Remove Disabled, keep other annotations, and rename to SKIPTHIS..."
  actions:
  - rewrite:
      to: "{{#sed}}s/(@Disabled\n.*@Test)//,{{{ modifierList }}}{{/sed}}\n\
        {{{ returnTypeElement }}} SKIPTHIS{{{ nameIdentifier }}}{{{ parameterList\
        \ }}}{{{ body }}}"
      target: "self"


Here, I added an additional line in the new Quick Fix.

{{#sed}}s/(@Disabled\n.*@Test)//,{{{ modifierList }}}{{/sed}}


This takes the modifier list, encodes it as a string, then uses sed to remove the line with @Disabled from the string, but leaves all other lines in the modifier, i.e. it leaves all other annotations alone.

NOTE: Remember to add the "," in the sed, otherwise you will see a comment added to your preview. This is how Sensei alerts you to syntax errors in the sed command.

/* e.g: {{#sed}}s/all/world/,helloall{{/sed}} */


Nested sed calls

I was lucky that I could match both the @Disabled and @Test in a single search and replace.

If the code is more complicated and I wanted to have a sequence of sed commands then I can do that by nesting them:

{{#sed}}s/@Test//,{{#sed}}s/@Disabled\n//,{{{ modifierList }}}{{/sed}}{{/sed}}


In the above example, I apply the @Test replacement to the results of applying the @Disabled replacement on the {{{ modifierList }}}.


Summary

sed is a very flexible way to achieve code rewriting and it is possible to nest the sed function calls for complicated rewrite conditions.

Recipes like this often end up being temporary because we are using them to improve our programming process, and once we have built up the muscle memory and no longer use the poor programming pattern we can remove or disable them in the Cookbook.


---

You can install Sensei from within IntelliJ using "Preferences \ Plugins" (Mac) or "Settings \ Plugins" (Windows) then just search for "sensei secure code".
All the code for this blog post can be found on GitHub in the `junitexamples` module of our blog examples repository https://github.com/SecureCodeWarrior/sensei-blog-examples

标语

在 AI 驱动的开发成果发布前实现有效治理

评估 AI 辅助风险,在提交代码时强制执行安全编码策略,并加速整个软件开发生命周期的安全交付。

预约演示
资源库

探索更多博客

获取有关安全编码、人工智能治理和软件风险管理的专家内容。

浏览全部
案例研究
筛选标签
这是 div 块中的一段文本。

Supercharged Security Awareness: How Tournaments are Inspiring Developers at Erste Group

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.

了解更多
案例研究
筛选标签
这是 div 块中的一段文本。

Security as culture: How Blue Prism cultivates world-class secure developers

Learn how Blue Prism, the global leader in intelligent automation for the enterprise, used Secure Code Warrior's agile learning platform to create a security-first culture with their developers, achieve their business goals, and ship secure code at speed

了解更多
案例研究
筛选标签
这是 div 块中的一段文本。

统一的安全文化:Sage 如何打造其安全倡导者项目

了解 Sage 如何通过灵活且以关系为导向的方法提升安全性,成功培养了 200 多名安全倡导者,并实现了显著的风险降低。

了解更多
博客
筛选标签
这是 div 块中的一段文本。

2026年网络安全意识月:理解人工智能安全对企业防御至关重要

目前,大多数顶级人工智能公司已公开呼吁全球放缓人工智能的发展速度,以便安全防护措施能够跟上步伐。然而事实是,严重的安全问题已经显现。随着人工智能实施带来的经济成本和风险不断累积,无数安全领导者和高管团队正处于紧迫的境地。

了解更多
博客
筛选标签
这是 div 块中的一段文本。

赋能要素 8:打造项目品牌

成功的安全编码项目不仅需要优质的内容。赋能要素 8 展示了如何通过品牌化将参与过程转变为一种令人向往、以地位为驱动的体验。

了解更多
博客
筛选标签
这是 div 块中的一段文本。

每一位员工现在都处于 AI 网络安全的最前线

企业技术格局正以一种鲜有人预料到的速度发生剧变,直到为时已晚。我们已正式跨越了从人工编写代码和基础辅助编程,进入“智能体开发生命周期”(ADLC)的时代。虽然自主 AI 智能体有望在多个职能领域实现前所未有的效率,但它们也带来了全新的安全和监管风险。

了解更多

在产品发布前确保 AI 驱动的开发安全

洞察开发风险,执行安全策略,并在整个软件开发生命周期中预防漏洞。

预约演示
未找到相关项目。