乐趣区

关于阿里云:80分钟100分83行代码决赛优秀选手如何解题

由阿里云云效主办的 2021 年第 3 届 83 行代码挑战赛曾经收官。超 2 万人围观,近 4000 人参赛,85 个团队组团来战。大赛采纳游戏闯关玩儿法,交融元宇宙科幻和剧本杀元素,让一众开发者玩得不可开交。

本次大赛最初一道题考验的是参赛者的 Debug 能力,最好对基于 springboot 的 spring webflux(至多是 spring mvc),spring security 有肯定理解,能够省去很多在较量过程中查找材料的工夫。
上面站在一个对上述架构不那么相熟的角度, 按步骤解说 debug 思路. 对于当前理解 spring 全家桶还是很有帮忙的。

第一步

Bug1

ReactiveWebSocketHandlerTest 单元测试调试,首先执行单元测试,查看执行后果。

调用栈从上向下剖析,看到是一个 EOF 谬误,即流在读取结束后依然尝试读取,找到谬误栈中最近的 com.aliyun.code83 结尾的源代码上下文。

private static CheckedFunction<DataInputStream, String> charsetNameDecoder = (DataInputStream input) -> {byte[] charsetNameBytes = input.readNBytes(input.readByte()); // 源代码第 100 行

        if (charsetName.get() == null) {charsetName.set(new String(charsetNameBytes, ISO_8859_1));
        }

        return charsetName.get();};

能够看到谬误出在第 100 行的 input.readByte()中,然而在上文中并没有其余的 input 操作,阐明这个流在传入时就曾经被读光了,顺着谬误栈持续向上找,Utils 的 89 行上下文。

public static String decodeMessage(byte[] rawMessage) {ByteArrayInputStream in = new ByteArrayInputStream(rawMessage);
        DataInputStream dis = new DataInputStream(in);

        try {return new String(dis.readAllBytes(), charsetNameDecoder.apply(dis)); // 源代码第 89 行
        } catch (IOException e) {e.printStackTrace();
            return String.format("%s<_>-<_>.", e.getClass().getSimpleName()); // 此行勿动,影响评分
        }
    }

从 89 行能够看到:charsetNameDecoder.apply(dis) 报了错,起因是 new String 的第一个参数 dis.readAllBytes()曾经把数据读完了,这里咱们须要大略剖析一下这部分代码的性能。

new String 如果传入 2 个参数的话,第一个参数 bytes[]是对应内容的 byte 数组,而第二个参数是字符集。这里看起来无论是字符串内容还是字符集都来自于 input 输出流,通过 charsetNameDecoder 逻辑来看,先通过 input.readByte 读出一个长度 N 来,在通过 readNBytes 读出长度 N 的局部解析出字符集,再把残余的局部读出来依照字符集进行字符串构建。

如果残缺读过所有代码的话,也能够从 ReactiveWebSocketHandler 的 javadoc 上看到包的格局,这里也体现出 javadoc 的重要性,做题前兴许代码不必读完,然而尽量把 javadoc 都过一下。

/**
 * 二进制包格局
 * byte 字符集长度; n1
 * byte[n1] 字符集数据;n1 = 字符集长度
 * byte[n2] 无效数据;n2 = 包总长度 - n1 - 1
 */
@Component("ReactiveWebSocketHandler")
public class ReactiveWebSocketHandler implements WebSocketHandler {...

这里问题显著出 89 行上, readAllBytes 提前把所有数据都读完了, 所以代码调整如下

  public static String decodeMessage(byte[] rawMessage) {ByteArrayInputStream in = new ByteArrayInputStream(rawMessage);
        DataInputStream dis = new DataInputStream(in);

        try {
            // 先从流中前局部读出字符集, 剩下的再通过 readAllBytes 读出
            final String charset = charsetNameDecoder.apply(dis);
            
            return new String(dis.readAllBytes(), charset);
        } catch (IOException e) {e.printStackTrace();
            return String.format("%s<_>-<_>.", e.getClass().getSimpleName()); // 此行勿动,影响评分
        }
    }

从新运行单元测试,发现 ReactiveWebSocketHandlerTest 曾经没有谬误了 (至多满足的 unit test 的判断冀望, 业务上是否有谬误未必)。

行第二个单元测试 Round4ApplicationTests,看起来是空的,间接胜利下一个。

Bug2

执行第三个单元测试 UtilsTest。

执行失败,看起来是一个字符串解决的逻辑,而解决的后果不太对。

上面察看一下测试用例:

Triplet.with(
        "提取一般文本",
        "Welcome to <pre>DevStudio</pre>",
        "Welcome to DevStudio"
),
Triplet.with(
        "提取 CJK 文本",
        "有 <i> 对象 </i> 了么? 别慌, 送你一个! 支付请加钉钉群: <quote>35991139</quote>",
        "有对象了么? 别慌, 送你一个! 支付请加钉钉群: 35991139"
),
Triplet.with(
        "提取 Tag 文本",
        "<p>Cosy 提效补全用过没, 还能搜搜搜 https://developer.aliyun.com/tool/cosy</p>",
        "Cosy 提效补全用过没, 还能搜搜搜 https://developer.aliyun.com/tool/cosy"
),
Triplet.with(
        "提取嵌套 tag 文本",
        "<blockquote><p>401?!! 不要慌,不要急,App Observer 帮忙您~ https://help.aliyun.com/document_detail/326231.html 理解一下 </p></blockquote>",
        "401?!! 不要慌,不要急,App Observer 帮忙您~ https://help.aliyun.com/document_detail/326231.html 理解一下"
),
Triplet.with(
        "万圣节惊喜小剧场",
        "<happy> 碧油鸡全副退散, 颈腰椎早日康复! </happy> 贼真挚",
        "碧油鸡全副退散, 颈腰椎早日康复! 贼真挚"
)

从每个用例能够看出,仿佛对于解决逻辑的冀望是把所有中括号里的元素去掉,就像打消 html 节点定义,只保留文本内容一样。上面察看一下理论被测试的办法:

private static final Pattern REGULAR_HTML_TAG = Pattern.compile("<(?<tag>.*)>");

public static String stripHtmlTag(String html) {if (ObjectUtils.isEmpty(html)) {return null;}

    StringBuilder builder = new StringBuilder();
    final Matcher matcher = REGULAR_HTML_TAG.matcher(html);
    while (matcher.find()) {matcher.appendReplacement(builder, Strings.EMPTY);
        if (log.isDebugEnabled()) {log.debug("remove tag {}", matcher.group("tag"));
        }
    }
    return builder.toString();}

确实, 从整体逻辑看起来是通过正则匹配一对 <>, 并替换成空的逻辑. 首先剖析最下面定义的正则表达式, 乍一看是 OK 的, 匹配两端为 <> 的任意字符.* , ? 是给匹配分组命名用的, 对于匹配无间接作用, 是 replace 时作为 group 的 key 看待, 这个具体能够查正则相干文档. 然而 UT 执行显著有错, 咱们把一个用例字符串用这个正则匹配看看

能够看到,正则匹配从第一个 < 间接到了最初结尾的 >,所以执行后果就是整句话替换还剩一个 ” 有 ” 字。这里波及到正则的贪心匹配问题,默认为贪心的,尽可能匹配更多内容,而勾销贪心的做法是在匹配规定前面加一个问号?变成

private static final Pattern REGULAR_HTML_TAG = Pattern.compile("<(?<tag>.*?)>");

bug3

改掉后再执行一次 UT

看起来好多了! 只有一个谬误了,当初来剖析一下为什么错。

public static String stripHtmlTag(String html) {if (ObjectUtils.isEmpty(html)) {return null;}

    StringBuilder builder = new StringBuilder();
    final Matcher matcher = REGULAR_HTML_TAG.matcher(html);
    while (matcher.find()) {matcher.appendReplacement(builder, Strings.EMPTY);
        if (log.isDebugEnabled()) {log.debug("remove tag {}", matcher.group("tag"));
        }
    }
    return builder.toString();}

从循环上看,对于 html 进行 tag 匹配,找到的话向 builder 里写入新字符串,而新字符串的内容是截止到匹配局部地位的文本,并且把 <(?.*?)> 替换为空。针对 “ 碧油鸡全副退散, 颈腰椎早日康复! 贼真挚 ” 这个用例。

• 第一次匹配内容是 <happy>,向 builder 里写入替换文本为空;
• 第二次匹配的局部是碧油鸡全副退散,颈腰椎早日康复! </happy>,向 builder 里追加后的内容是 ” 碧油鸡全副退散,颈腰椎早日康复!”
• 第三次 while 过去,因为没有新的 <.*> 内容, 间接完结循环,return 了!所以问题出在这里,须要把剩下的局部 ” 贼真挚 ” 补进来。

于是代码调整如下:

public static String stripHtmlTag(String html) {if (ObjectUtils.isEmpty(html)) {return null;}

    StringBuilder builder = new StringBuilder();
    final Matcher matcher = REGULAR_HTML_TAG.matcher(html);
    while (matcher.find()) {matcher.appendReplacement(builder, Strings.EMPTY);
        if (log.isDebugEnabled()) {log.debug("remove tag {}", matcher.group("tag"));
        }
    }

    matcher.appendTail(builder);
    
    return builder.toString();}

减少 appendTrail,把剩下的局部补进来。运行单元测试, 所有 OK!

其实,这里还有个简略写法:

public static String stripHtmlTag(String html) {if (ObjectUtils.isEmpty(html)) {return null;}

    return html.replaceAll("<.*?>", "");
}

不过, 既然改 bug,那尽量保留原有逻辑为好。

第二步

上面咱们开始进行业务调试,依照 README 提醒运行。/round4. 收场就挂了,看到揭示须要启动服务才行。

找到带 @SpringBootApplication 注解的 main 办法,这是 spring boot 程序规范的启动入口,启动, run/debug 都可,如果想要断点调试的话, 用 debug。

Bug4

再次执行./round4

Step1 看起来没啥错,Step2 呈现谬误,看起来是冀望动静增加一个用户 reporter,失败了,谬误音讯是短少 CSRF 申请头,如果用过 spring security (无论在 spring MVC 还是 spring Webflux)的话, 在平安配置外面可能会留下印像,就是对 csrf 的配置。这里看到 round4 客户端仿佛申请中不带有 csrf 的 token,那咱们只能改服务了。

注: CSRF 百度一下能够理解它的作用, 目标和根本机制,spring security 有原生实现,只有通过配置解决就好,csrf 性能默认是开启的。

找到平安配置的类 WebSecurityConfig,调整配置如下:

@Bean
public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
    return http
            .headers().disable()
            .authorizeExchange()
            .pathMatchers("/endpoints").hasAnyRole("USER")
            .pathMatchers("/users").hasAnyRole("admin")
            .pathMatchers("/ws/test").hasAnyRole("TEST") // 该行勿改变,否则影响评分
            .pathMatchers("/ws/**").hasAnyRole("admin")
            .anyExchange().authenticated()
            .and()
            .httpBasic()
            .and()
            .formLogin().disable()
      // 退出这一行
            .csrf().disable()
            .build();}

bug5

重启服务, 再次执行./round4

第二步又挂了,然而谬误内容变了,变成了 401,看形容是身份凭证不对,谬误日志很贴心的打出了谬误凭证内容,是 basic auth 形式,前面有一串字符。

一看 = 结尾的乱码字符,会比拟容易联想到 base64。轻易找个 base64 解密工具,把这串文字放进去。

解进去一看,很典型的账号: 明码格局,也就是尝试用 admin / admin123 作为账号密码解决失败了。回到 WebSecurityConfig 类查看配置

@Bean
public MapReactiveUserDetailsService userDetailsService() {UserDetails user = User.builder()
            .username("user")
            .password("{noop}user")
            .roles("USER")
            .build();
    UserDetails admin = User.builder()
            .username("admin")
            .password("{noop}admin")
            .roles("ADMIN")
            .build();
    return new MapReactiveUserDetailsService(user, admin);
}

看到 admin 的配置 password 仿佛是 admin,至于 {noop} 是什么意思,如果有精力调试的话,能够跟进去看下,UserDetailService 对于明码治理是应用一个 PasswordEncoder 接口来解决的,因为输出明码时尽管时明文,然而平安起见明码在数据库中要混同过才能够存储,否则数据库数据泄露的话结果是灾难性的。而 PasswordEncoder 有很多的实现类,UserDetailService 默认应用的是一个叫做 DelegatingPasswordEncoder 的类,它会依据状况把明文交给不同的 PasswordEncoder 解决成密文匹配,而这个 ” 状况 ” 就是后面大括号的内容。上面是 DelegatePasswordEncoder 注册的各种混同算法。

public final class PasswordEncoderFactories {private PasswordEncoderFactories() { }

    public static PasswordEncoder createDelegatingPasswordEncoder() {
        String encodingId = "bcrypt";
        Map<String, PasswordEncoder> encoders = new HashMap();
        encoders.put(encodingId, new BCryptPasswordEncoder());
        encoders.put("ldap", new LdapShaPasswordEncoder());
        encoders.put("MD4", new Md4PasswordEncoder());
        encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
        encoders.put("noop", NoOpPasswordEncoder.getInstance());
        encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
        encoders.put("scrypt", new SCryptPasswordEncoder());
        encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
        encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));
        encoders.put("sha256", new StandardPasswordEncoder());
        encoders.put("argon2", new Argon2PasswordEncoder());
        return new DelegatingPasswordEncoder(encodingId, encoders);
    }
}

能够看到,noop 对应的是一个叫 NoOpPasswordEncoder 的实例,也就是 no operation,明文拿来什么都不干间接明文存储或比拟,所以这样也不便了咱们批改。

总之 {noop} 能够看不明确是怎么回事,然而看不明确的货色不要碰,只碰明确的,admin 还是意识的改成{noop}admin123。

Bug6

重启服务,再次执行./round4


Step2 又换个谬误持续挂 ….

这次看到谬误是权限谬误了,然而到底须要什么权限,从这里看不出来。这时候咱们回到 Web 服务的控制台找线索

看到服务日志中,尝试调用 POST /users 后,报出了一个 403 谬误,应该跟 round4 的谬误对的上的,也就是说可能是 /users 接口权限有问题,回到代码查看

public class WebSecurityConfig {

    @Bean
    public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
        return http
                .headers().disable()
                .authorizeExchange()
                .pathMatchers("/endpoints").hasAnyRole("USER")
                .pathMatchers("/users").hasAnyRole("admin")
                .pathMatchers("/ws/test").hasAnyRole("TEST") // 该行勿改变,否则影响评分
                .pathMatchers("/ws/**").hasAnyRole("admin")
                .anyExchange().authenticated()
                .and()
                .httpBasic()
                .and()
                .formLogin().disable()
                .csrf().disable()
                .build();}

    @Bean
    public MapReactiveUserDetailsService userDetailsService() {UserDetails user = User.builder()
                .username("user")
                .password("{noop}user")
                .roles("USER")
                .build();
        UserDetails admin = User.builder()
                .username("admin")
                .password("{noop}admin123")
                .roles("ADMIN")
                .build();
        return new MapReactiveUserDetailsService(user, admin);
    }

}

从配置中看,admin 账号具备一个角色叫做 ”ADMIN”,而下面 /users 接口的配置是须要权限 ”admin”。这个不像数据库对大小写不敏感,身份权限这么谨严的货色,一个字母都不 能差,把下面的 admin 全都改成大写 ADMIN。

Bug7

重启服务再试

可喜可贺! Step2 跑通了,不论它干了啥,总之是跑通了! 而后解决 Step3,又是权限问题,然而咱们不晓得到底是什么问题,如果账号密码谬误的话是 401,而且 reporter 是 Step2 动静加进去的,要是明码对不上那也没方法调整。

从方才 Step2 调试教训来看,403 Forbidden 的起因很可能出在权限下面,然而咱们也不晓得 reporter 的权限是啥,这时候就要给 /users 打个断点了,看看 Step2 到底放进来了个啥。

找到 Round4Controller,addUser 办法打个断点,执行./round4

看到 Step2 调用接口是参数 username 是 reporter,明码是 reporter,还有一个特地的字段,叫做 authorities,内容是 ROLE_REPORTER,看起来很可疑,仿佛是给这个用户赋予权限。

而 Step3 调用的接口是 /ws/DevStudio、/ws/Cosy、WebSecurityConfig 中匹配的规定是

.pathMatchers("/ws/**").hasAnyRole("ADMIN")

目前服务器的配置来看,只有 ADMIN 角色能够拜访,那就须要给它开后门了,看看 hasAnyRole 是个数组类型参数,追加一个值 ”ROLE_REPORTER”

.pathMatchers("/ws/**").hasAnyRole("ADMIN","ROLE_REPORTER")

再试,这次应该成 ….. 又挂了! 谬误还是一样?

那就是说方才增加的角色不对?

这时候察看一下配置,其余的角色都叫 ADMIN、USER、TEST 就是这个后面加了个 ROLE_很奇怪,而且在 POST /users 中,这个值放在一个叫做 authorities 的数组里名字很宽泛,没有特指 Role,会不会像方才的{noop}ADMIN123 一样,后面的 ROLE_是一个潜规则?如果是个 PERMISSION_之类的话就是权限了?那这么说这个角色可能就是叫做 REPORTER,从新调整配置

.pathMatchers("/ws/**").hasAnyRole("ADMIN", "REPORTER")

再试! 可喜可贺, 有变动了! 而且提醒能够打分了,调用./round4 –submit,忽视掉三个灵魂问题后,总算有分数了,然而显著问题多多。看来,只是走通了,然而后果不尽如人意。

Bug8

从新执行./round4 察看输入,能够看到满外面乱码,而且简直没有一个失常的中文,联合后面 Unit Test 调试,能够猜想这可能跟字符集解决无关,也就是 Utils 那个类应该还是有 bug。
这时候从输入也大略能看进去些这个程序的目标了, 仿佛是承受 round4 的申请, 输入一些文本。而这些业务接口入口就是 /ws/**。

开始寻找 /ws/** 到底是怎么映射到这些办法的,首先在 WebConfig 找到可疑办法

    @Autowired
    @Qualifier("ReactiveWebSocketHandler")
    private WebSocketHandler webSocketHandler;

    @Bean
    public Map<String, WebSocketHandler> webSocketUrlMap() {return Utils.randomWords(3)
                .stream()
                .map(w -> "/ws/" + w)
                .collect(Collectors.toMap(Function.identity(), w -> webSocketHandler));
    }

仿佛注册了一个 Map 类型的 Bean,key 是 /ws 结尾的地址,value 是 WebSocketHandler,而 WebSocketHandler 的定义中,申明了在 spring 容器中,它的名字是 ReactiveWebSocketHandler。接着全文寻 ReactiveWebSocketHandler 文本,发现另一个可疑的类。

Component("ReactiveWebSocketHandler")
public class ReactiveWebSocketHandler implements WebSocketHandler {
    @Override
    public Mono<Void> handle(WebSocketSession session) {
        return session.send(session.receive()
                        .map(WebSocketMessage::getPayload)
                        .map(getBufferConverter())
                        .map(Utils::decodeMessage)
                        .map(Utils::stripHtmlTag)
                        .log()
                        .map(session::textMessage));
    }

    private Function<DataBuffer, byte[]> getBufferConverter() {final byte[] buffer = new byte[1024];
        return (DataBuffer dataBuffer) -> {int length = dataBuffer.readableByteCount();
            dataBuffer.read(buffer, 0, length);
            return buffer;
        };
    }
}

看起来很有关系,handle 办法看起来是解决文本的,至于 Mono 是什么,baidu 一下和 WebFlux 无关,兴许不是太懂,然而看着一连串的 map 办法,如果 java8 的个性相熟的话,很像 Collections.stream()前面或者 Optional 类中。map 的应用办法,是一连串的映射逻辑,从办法名大略猜测各自的性能:

  1. WebSocketMessage::getPayload 取得申请体;
  2. getBufferConverter() 转换成个 buffer;
  3. Utils::decodeMessage 解码;
  4. Utils::stripHtmlTag 去掉 tag;
  5. log() 打印日志;
  6. session::textMessage 向会话输入文本;

其中 1,6 都是 spring 的办法,出 bug 的可能性微不足道,log 个别也出不了啥错,问题可能就在 2,3,4 下面。

首先乱码一大片,感觉少不了 decodeMessage 的干系,剖析源代码:

    public static String decodeMessage(byte[] rawMessage) {ByteArrayInputStream in = new ByteArrayInputStream(rawMessage);
        DataInputStream dis = new DataInputStream(in);

        try {final String charset = charsetNameDecoder.apply(dis);

            return new String(dis.readAllBytes(), charset);
        } catch (IOException e) {e.printStackTrace();
            return String.format("%s<_>-<_>.", e.getClass().getSimpleName()); // 此行勿动,影响评分
        }
    }

    private static final ThreadLocal<String> charsetName = new ThreadLocal<>();

    private static final CheckedFunction<DataInputStream, String> charsetNameDecoder = (DataInputStream input) -> {byte[] charsetNameBytes = input.readNBytes(input.readByte());

        if (charsetName.get() == null) {charsetName.set(new String(charsetNameBytes, ISO_8859_1));
        }

        return charsetName.get();};

这两个类做过 Unit Test,阐明硬伤不大,然而代码行数不多,缓缓剖析。

decodeMessage 逻辑绝对清晰,看起来没大问题,那持续看 charsetNameDecoder。

charsetNameDecoder 很特地的是用了一个 ThreadLocal 来存储信息,而 WebSocket 是不是每次申请都用新的线程来解决兴许不分明,会不会因为线程池重用导致 ThreadLocal 被净化也不那么分明,然而至多我吃力解析出了 charset,仅因为 threadlocal 存在数据就不必我解析的了就不太对啊。
从这代码上的意思看起来像,如果能解析出 charset 最好,如果解析不进去的话,用之前解析进去的,所以依照这个思路调整代码:

 private static final CheckedFunction<DataInputStream, String> charsetNameDecoder = (DataInputStream input) -> {byte[] charsetNameBytes = input.readNBytes(input.readByte());

        if (charsetNameBytes != null && charsetNameBytes.length > 0) {charsetName.set(new String(charsetNameBytes, ISO_8859_1));
        }

        return charsetName.get();};

再试!

Bug9

可喜可贺,能够看得出来后果中的乱码显著缩小,而且呈现了失常的中文,阐明字符集的批改有了成果,而且从输入看,仿佛在尝试输入一些 java 代码。

再仔细观察乱码的法则,仿佛都是在每一行的最初呈现,乱码后面的局部看起来都还好。会不会是流太长的起因?

转向前一个步骤 ReactiveWebSocketHandler::getBufferConverter

  private Function<DataBuffer, byte[]> getBufferConverter() {final byte[] buffer = new byte[1024];
        return (DataBuffer dataBuffer) -> {int length = dataBuffer.readableByteCount();
            dataBuffer.read(buffer, 0, length);
            return buffer;
        };
    }

看起来是构建一个长度为 1024 的数组,而后把 length 长度的内容填充进去 … 等等?为啥晓得 length 了还固定搞个 1024?10 月 24 号很吉利还是咋的?

改了再跑!

   private Function<DataBuffer, byte[]> getBufferConverter() {return (DataBuffer dataBuffer) -> {int length = dataBuffer.readableByteCount();
            final byte[] buffer = new byte[length];
            dataBuffer.read(buffer, 0, length);
            return buffer;
        };
    }

Bug10

乱码都隐没了! 仿佛都 OK 了! 这时候扫一眼 web 服务的控制台,咋输入了一堆谬误?

经典谬误 NPE,看这一排的 onNext 就感觉跟那一排的 map 有关系,难道说传下来的数据不能有 null?每个步骤检查一下,在 Utils::stripHtmlTag 找到可疑代码。

   if (ObjectUtils.isEmpty(html)) {return null;}

    StringBuilder builder = new StringBuilder();
    final Matcher matcher = REGULAR_HTML_TAG.matcher(html);
    while (matcher.find()) {matcher.appendReplacement(builder, Strings.EMPTY);
        if (log.isDebugEnabled()) {log.debug("remove tag {}", matcher.group("tag"));
        }
    }

    matcher.appendTail(builder);

    return builder.toString();}

如果传入 html 是空的,返回一个 null,何必呢,把 null 改成 “” 再试。

终于没有乱码和谬误了,提交评分! 90 分!

持续找 bug! 找了有 10 分钟也没看出哪里错来,而后无奈再看了下 README,发现最初 10 分是那 3 个问题的分数 …

不看文档害死人啊 …

至于最初三个问题,我是缓缓试出来的,正确答案请看主办方的解读吧 …

最初

在理论工作当中,是很禁忌在没有浏览代码搞清性能的前提下 debug 的,因为平时没有 round4 给咱们打分,很容易改掉一个 bug,又带来一群新的。

所以,以上的攻略是基于较量环境,在有明确的评分零碎存在时谋求速度的一种做法思路,并不举荐应用在日常工作中,当然 debug 中寻找谬误的思路是共通的。

如果有短缺的工夫理解业务背景 (较量也不会给具体的 prd…) 和技术思路的前提下,这些水平的 bug 应该大部分都能够肉眼间接发现排除,当然日常工作中不会有这么多底层的低级 bug 呈现,如果开发天天面对这种 bug,架构师就能够拿来祭旗了 …

大赛目前全副关卡凋谢体验,域名地址:https://code83.ide.aliyun.com/,欢送你来。

                                       举荐浏览

1、用代码玩剧本杀?第 3 届 83 行代码大赛剧情官网解析
2、无算法不 Java,这道算法题很难?

欢送大家应用云效,云原生时代新 DevOps 平台,通过云原生新技术和研发新模式,大幅晋升研发效率。现云效公共云根底版不限人数 0 元应用。

退出移动版