def halts(f):
# false, если программа зависает
# true, если завершается за конечное время
@inponomarev
Иван Пономарёв, КУРС/МФТИ
Wikipedia: «Анализ программного обеспечения, производимый без реального выполнения исследуемых программ».
Здравый смысл: Любая проверка исходного кода, требующая только исходный код (без тестов).
Зависнет или остановится?
def halts(f):
# false, если программа зависает
# true, если завершается за конечное время
def g():
if halts(g):
while(True):
pass
Вычисляет ли функция квадрат числа?
def is_a_squaring_function(f):
# true, если функция вычисляет квадрат
# false, если не вычисляет
def halts(f):
def t(n):
f()
return n * n
return is_a_squaring_function(t)
object has no attribute
(если динамическая типизация)
NPE
(если нет Null Safety)
Близкое динамическое болото | Труднодоступное статическое озеро |
|
|
Google <Your Language> static analyzer
Google <Your Language> linter
bin/format.sh
— форматирование кода
bin/inspect.sh
— инспекции (с выводом в .xml)
Всё!
Однократное применение анализа бессмысленно
Анализ должен производиться непрерывно и автоматически
Результаты анализа должны определять quality gates
Jez Humble, David Farley. Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation. Addison-Wesley, 2011 |
«Фильтрующая способность»
Сложность, стоимость,
время работы, вероятность сбоя
Размер пропускаемого загрязнения
Пропускная способность
Сложность, стоимость
Статанализ — «фильтр грубой очистки» в начале каскада фильтров
В отдельности от других — не работает
Другие без него работают хуже
resource.json
{
"key": "value with "unescaped quotes" "
}
Все UI тесты падают.
Но это происходит спустя дни.
Добавляем JSONLint в начало пути
find . -name \\*.json -print0 | xargs -0 -n1 -t jsonlint -q
Отклик на проблему идёт сразу
PROFIT
Знакомая картина?
Оставить нельзя пофиксить!
Google + Stackoverflow:
'sed remove trailing spaces'
find . -name '*.py' -print0 | xargs -0 -n1 -t \
sed -i -r 's/\s+$//'
'bash add a newline to the end of a file'
find . -name '*.java' -print0 | xargs -0 -L1 bash \
-c 'test "$(tail -c 1 "$0")" && printf "\r\n" >> $0'
etc etc
Javascript: eslint --fix
Spotless can format
<java | kotlin | scala | sql | groovy | javascript | flow | typeScript | css | scss | less | jsx | vue | graphql | json | yaml | markdown | license headers | anything>
using
<gradle | maven | anything>
«Если меньше 100 находок, то код ОК»
ДАНО: в коде 90 находок и код ОК.
Добавляем Null Pointer Dereference.
У нас 91 находка, код всё ещё ОК?
Вывод: не используйте данный метод!
Старые находки — в игнор
Новые находки — не пропускаем
Наивный подход:
<file name="AppProperties.java">
<error line="31" column="5" message="Missing a Javadoc comment."/>
<error line="36" column="5" message="Missing a Javadoc comment."/>
</file>
Добавляем текст в начало файла…
…номера строк "уползли" и все находки снова появились.
Подход PVS-Studio --- хеши строк:
{
"FileName": "CelestaParser.java",
"ErrorCode": "V6021",
"CodePrev": -1464702071,
"CodeCurrent": -1679070819,
"CodeNext": 35764079
}
Вывод: метод хорош, но труднодоступен
Проверка документации:
Проверка литералов и комментариев:
|
Храните пользовательский словарь в проекте
Quality Gate: не должно быть незнакомых спелчекеру слов.
Вывод: spellchecker может быть частью пайплайна
Вид метаданных:
# warnings.yml
celesta-sql:
checkstyle: 434
spotbugs: 45
celesta-core:
checkstyle: 206
spotbugs: 13
celesta-maven-plugin:
checkstyle: 19
spotbugs: 0
celesta-unit:
checkstyle: 0
spotbugs: 0
Jenkins scripted pipeline
Jenkins shared libraries in Groovy
JFrog Artifactory для хранения метаданных о сборках
<checkstyle>
<file name="...">
<error line="1" severity="..." message="..."/>
</file>
...
</checkstyle>
private Map countModule(prefix) {
def count = [:]
def f = new File("${prefix}/target/checkstyle-result.xml")
if (f.exists()) {
def checkstyle = new XmlSlurper().parseText(f.text)
count.put("checkstyle", checkstyle.file.error.size())
}
. . .
count
}
def server = Artifactory.server 'ART'
def downloadSpec = """
{"files": [
{
"pattern": "warn/${project}/*/warnings.yml",
"build": "${project} :: dev/LATEST",
"target": "previous.yml",
"flat": "true"
}
]
}"""
server.download spec: downloadSpec
oldWarnings = readYaml file: 'previous.yml'
stage ('Ratcheting') {
def warningsMap = countWarnings()
writeYaml file: 'target/warnings.yml', data: warningsMap
compareWarningMaps oldWarnings, warningsMap
}
Собирает и читает отчёты всех известных анализаторов
def checkstyle
= scanForIssues tool: checkStyle(pattern: '**/cs.xml')
def spotbugs
= scanForIssues tool: spotBugs(pattern: '**/spotbugs.xml')
def idea
= scanForIssues tool: ideaInspection(pattern: 'target/idea_inspections/*.xml')
def eslint
= scanForIssues tool: esLint(pattern: '**/eslint.xml')
publishIssues issues: [checkstyle, spotbugs, idea, eslint]
. . .
Красиво отображает
Можно программировать Quality Gates, в т. ч. в виде разницы с reference build:
recordIssues tool: java(pattern: '*.log'),
qualityGates: [[threshold: 1,
type: 'TOTAL',
unstable: true]]
Кто здесь видит проблему?
#.travis.yml
. . .
install:
- pip install yamllint
- pip install ansible-lint
script:
. . .
# Check YAML validity
- yamllint -c yamllint.yml .
# Ansible code static analysis
- ansible-lint . . .
- ansible-lint . . .
- ansible-lint . . .
#.travis.yml
. . .
install:
- pip install yamllint==1.13.0
- pip install ansible-lint==3.5.1
Humble, Jez; Farley, David (2011). Continuous Delivery: reliable software releases through build, test, and deployment automation.
Иван Пономарев Внедряйте статический анализ в процесс, а не ищите с его помощью баги
Иван Пономарев Запускаем инспекции IntelliJ IDEA на Jenkins
Алексей Кудрявцев Анализ программ: как понять, что ты хороший программист
@inponomarev
Спасибо!