Хаос не отслеживать ошибки и запросы функций при разработке программного обеспечения. Наличие простого средства отслеживания проблем сделало бы управление проектом гораздо более успешным. Теперь мне нравятся простые вещи, и я думаю, что для небольшого проекта, наличие этого трекера прямо внутри системы управления версиями (особенно с DSVC, например, Mercurial / Git и т. Д.), Не только выполнимо, но и очень удобно. Вам не нужно сходить с ума от всех причудливых функций, но достаточно просто для отслеживания проблем. Я хотел бы предложить этот макет для вас.
Допустим, у вас есть проект, который выглядит так
project +- src/main/java/Hello.java +- issues/issue-001.md +- pom.xml
Все, что мне нужно, это простой каталог, issues
чтобы начать работу. Теперь у меня есть место для отслеживания моей проблемы! Первая проблема issue-000.md
должна быть о том, о чем ваш проект. Например:
/id=issue-001 /createdon=2012-12-16 18:07:08 /type=bug /status=new /resolution=open /from=zemian /to= /found=v1.0.0 /fixed= /subject=A simple Java Hello program # Updated on 2012-12-16 18:07:08 We want to create a Maven based Hello world program. It should print "Hello World."
Я выбираю .md
расширение файла для намерения писать комментарии в формате Markdown. Поскольку это текстовый файл, вы делаете то, что хотите. Чтобы быть более структурированным, я добавил несколько метаданных заголовков для отслеживания проблем. Давайте определим некоторые здесь. Я бы предложил использовать их и форматирование:
/id=issue- /createdon= /type=feature|bug|question /status=new|reviewing|working|testing|fixed /resolution=open|closed|rejected|hold /from= /to= /found= /fixed=
Это должно охватывать большинство проблем с ошибками и разработкой функций. Это не круто писать программы без истории изменений, включая эти проблемы. Итак, давайте использовать источник контроля. Я настоятельно рекомендую вам использовать Mercurial hg
. Вы можете создать и инициализировать новый репозиторий следующим образом.
bash> cd project bash> hg init bash> hg add bash> hg commit -m "My hello world project"
Теперь ваш проект создан, и у нас есть место для отслеживания ваших проблем. Теперь это простой текстовый файл, поэтому используйте ваш любимый текстовый редактор и редактируйте его. Однако создание новой проблемы с этими тегами заголовка скучно. Будет неплохо иметь скрипт, который немного справится с этим. У меня есть скрипт Groovy issue.groovy
(см. В конце этой статьи), который позволяет запускать отчеты и создавать новые проблемы. Вы можете добавить этот скрипт в свой project/issues
каталог и мгновенно создавать новые отчеты о проблемах и запросах! Вот пример вывода на моем ПК:
bash> cd project bash> groovy scripts/issue.groovy Searching for issues with /resolution=open Issue: /id=issue-001 /status=new /subject=A simple Java Hello program 1 issues found. bash> groovy scripts/issue.groovy --new /type=feature /subject='Add a unit test.' project\issues\issue-002.md created. /id=issue-002 /createdon=2012-12-16 19:10:00 /type=feature /status=new /resolution=open /from=zemian /to= /found=v1.0.0 /fixed= /subject=Add a unit test. bash> groovy scripts/issue.groovy Searching for issues with /resolution=open Issue: /id=issue-000 /status=new /subject=A simple Java Hello program Issue: /id=issue-002 /status=new /subject=Add a unit test. 2 issues found. bash> groovy scripts/issue.groovy --details /id=002 Searching for issues with /id=002 Issue: /id=issue-002 /createdon=2012-12-16 19:10:00 /found=v1.0.0, /from=zemian, /resolution=open, /status=new /type=feature /subject=Add a unit test. 1 issues found. bash> groovy scripts/issue.groovy --update /id=001 /status=fixed /resolution=closed 'I fixed this thang.' Updating issue /id=issue-001 Updating /status=fixed Updating /resolution=closed Update issue-001 completed.
Сценарий дает вам быстрый и последовательный способ создания / обновления / поиска проблем. Но это просто текстовые файлы! Вы также можете запустить ваш любимый текстовый редактор и изменить любую вещь, которую вы хотите. Сохраните и даже зафиксируйте его в вашем исходном хранилище. Все не будет потеряно.
Я надеюсь, что этот скрипт отслеживания ошибок поможет быстро запустить ваш следующий проект. Дай мне знать, что ты думаешь!
Наслаждайтесь!
Земян Вот мой issue.groovy
сценарий.
#!/usr/bin/env groovy // // A groovy script to manage issue files and its metadata/headers. // Created by Zemian Deng <[email protected]> 12/2012 // // Usage: // bash> groovy [java_opts] issue.groovy [option] [/header_name=value...] [arguments] // // Examples: // # Report all issues that match headers (we support RegEx!) // bash> groovy issue /resolution=open // bash> groovy issue /subject='Improve UI|service' // bash> groovy issue --details /status=fixed // // # Create a new bug issue file. // bash> groovy issue --new /type=bug /to=zemian /found=v1.0.1 /subject='I found some problem.' 'More details here.' // // # Update an issue // bash> groovy issue --update /id=issue-001 /status=fixed /resolution=closed 'I fixed this issue with Z algorithm.' // class issue { def ISSUES_HEADERS = ['/id', '/createdon', '/type', '/status', '/resolution', '/from', '/to', '/found', '/fixed', '/subject'] def ISSUES_HEADERS_VALS = [ '/type' : ['feature', 'bug', 'question'] as Set, '/status' : ['new', 'reviewing', 'working', 'testing', 'fixed'] as Set, '/resolution' : ['open', 'closed', 'rejected', 'hold'] as Set ] def issuesDir = new File(System.getProperty("issuesDir", getDefaultIssuesDir())) def issuePrefix = System.getProperty("issuePrefix", 'issue') def arguments = [] // script arguments after parsing def options = [:] // script options after parsing def headers = [:] // user input issue headers static void main(String[] args) { new issue().run(args) } // Method declarations def run(String[] args) { // Parse and save options, arguments and headers vars def headersSet = ISSUES_HEADERS.toSet() args.each { arg -> def append = true if (arg =~ /^--{0,1}\w+/) { options[arg] = true append = false } else if (arg =~ /^\/\w+=.*$/) { def words = arg.split('=') if (words.length == 2) { def name = words[0] def value = words[1] if (!headersSet.contains(name)) throw new Exception("ERROR: Unkown header name $name.") if (ISSUES_HEADERS_VALS[name] != null && !(ISSUES_HEADERS_VALS[name].contains(value))) throw new Exception("ERROR: Unkown header $name=$value. Allowed: ${ISSUES_HEADERS_VALS[name].join(', ')}") headers.put(name, words[1]) append = false } } if (append) { arguments << arg } } // support short option flag if (options['-d']) options['--details'] = true // Run script depending on options passed if (options['--help'] || options['-h']) { printHelp() } else if (options['--new'] || options['-n']) { createIssue() } else if (options['--update'] || options['-u']) { updateIssue() } else { reportIssues() } } def printHelp() { new File(getClass().protectionDomain.codeSource.location.path).withReader{ reader -> def done = false def line = null while (!done && (line = reader.readLine()) != null) { line = line.trim() if (line.startsWith("#") || line.startsWith("//")) println(line) else done = true } } } def getDefaultIssuesDir() { return new File(getClass().protectionDomain.codeSource.location.path).parentFile.path } def getIssueIds() { def issueIds = [] def files = issuesDir.listFiles() if (files == null) return issueIds files.each{ f -> def m = f.name =~ /^(\w+-\d+)\.md/ if (m) issueIds << m[0][1] } return issueIds } def getIssueFile(String issueid) { return new File(issuesDir, "${issueid}.md") } def reportIssues() { if (headers.size() == 0) headers['/resolution'] = 'open' def headersLine = headers.sort{ a,b -> a.key <=> b.key }.collect{ k,v -> "$k=$v" }.join(', ') println "Searching for issues with $headersLine" def count = 0 getIssueIds().each { issueid -> def file = getIssueFile(issueid) def issueHeaders = [:] file.withReader{ reader -> def done = false def line = null while (!done && (line = reader.readLine()) != null) { if (line =~ /^\/\w+=.*$/) { def words = line.split('=') if (words.length >= 2) { issueHeaders.put(words[0], words[1..-1].join('=')) } } else if (issueHeaders.size() > 0) { done = true } } } def match = headers.findAll{ k,v -> (issueHeaders[k] =~ /${v}/) ? true : false } if (match.size() == headers.size()) { def line = "Issue: /id=${issueHeaders['/id']}" if (options['--details']) { def col = 4 def issueHeadersKeys = issueHeaders.keySet().sort() - ['/id', '/subject'] issueHeadersKeys.collate(col).each { set -> line += "\n " + set.collect{ k -> "$k=${issueHeaders[k]}" }.join(" ") } line += "\n /subject=${issueHeaders['/subject']}" } else { line += " /status=${issueHeaders['/status']}" + " /subject=${issueHeaders['/subject']}" } println line count += 1 } } println "$count issues found." } def createIssue() { def ids = getIssueIds().collect{ issueid -> issueid.split('-')[1].toInteger() } def nextid = ids.size() > 0 ? ids.max() + 1 : 1 def issueid = String.format("${issuePrefix}-%03d", nextid) def file = getIssueFile(issueid) def createdon = new Date().format('yyyy-MM-dd HH:mm:ss') def newHeaders = [ '/id' : issueid, '/createdon' : createdon, '/type' : 'bug', '/status' : 'new', '/resolution' : 'open', '/from' : System.properties['user.name'], '/to' : '', '/found' : 'v1.0.0', '/fixed' : '', '/subject' : 'A bug report' ] // Override newHeaders from user inputs headers.each { k,v -> newHeaders.put(k, v) } //Output to file file.withWriter{ writer -> ISSUES_HEADERS.each{ k -> writer.println("$k=${newHeaders[k]}") } writer.println() writer.println("# Updated on ${createdon}") writer.println() arguments.each { writer.println(it) writer.println() } writer.println() } // Output issue headers to STDOUT println "$file created." ISSUES_HEADERS.each{ k -> println("$k=${newHeaders[k]}") } } def updateIssue() { def userHeaders = new HashMap(headers) userHeaders.remove('/createdon') // we should not update this field def issueid = userHeaders.remove('/id') // We will not re-update /id if (issueid == null) throw new Exception("Failed to update issue: missing /id value.") if (!issueid.startsWith(issuePrefix)) issueid = "${issuePrefix}-${issueid}" println("Updating issue /id=${issueid}") def file = getIssueFile(issueid) def newFile = new File(file.parentFile, "${file.name}.update.tmp") def hasUpdate = false def issueHeaders = [:] if (!file.exists()) throw new Exception("Failed to update issue: file not found for /id=${issueid}") // Read and update issue headers file.withReader{ reader -> // Read all issue headers first def done = false def line = null while (!done && (line = reader.readLine()) != null) { if (line =~ /^\/\w+=.*$/) { def words = line.split('=') if (words.length >= 2) { issueHeaders.put(words[0], words[1..-1].join('=')) } } else if (issueHeaders.size() > 0) { done = true } } // Find issue headers differences userHeaders.each{ k,v -> if (issueHeaders[k] != v) { println("Updating $k=$v") issueHeaders[k] = v if (!hasUpdate) hasUpdate = true } } // Update issue file if (hasUpdate) { newFile.withWriter{ writer -> ISSUES_HEADERS.each{ k -> writer.println("${k}=${issueHeaders[k] ?: ''}") } writer.println() // Write/copy the rest of the file. done = false while (!done && (line = reader.readLine()) != null) { writer.println(line) } writer.println() } } } // reader if (hasUpdate) { // Rename the new file back to orig file.delete() newFile.renameTo(file) } // Append any arguments as user comments if (arguments.size() > 0) { file.withWriterAppend{ writer -> writer.println() writer.println("# Updated on ${new Date().format('yyyy-MM-dd HH:mm:ss')}") writer.println() arguments.each{ text -> writer.println(text) writer.println() } println() } } println("Update $issueid completed.") } }